【01 dp】B006_NK_石头碰撞(问题转化)

一、Problem

给定一组石头,每个石头有一个正数的重量。每一轮开始的时候,选择两个石头一起碰撞,假定两个石头的重量为x,y,x<=y,碰撞结果为

  1. 如果x==y,碰撞结果为两个石头消失
  2. 如果x != y,碰撞结果两个石头消失,生成一个新的石头,新石头重量为y-x

最终最多剩下一个石头为结束。求解最小的剩余石头质量的可能性是多少。

输入描述:
第一行输入石头个数(<=100)

第二行输入石头质量,以空格分割,石头质量总和<=10000

输出描述:
最终的石头质量

输入例子1:
6
2 7 4 1 8 1

输出例子1:
1

二、Solution

方法一:贪心 + PQ(WA)

每次拿出两块质量最大的石头,如果质量不相同则进行碰撞,碰完加到堆中,可惜只能过 64% 样例…

import java.util.*;
import java.math.*;
import java.io.*;
public class Main{
    static class Solution {
        void init() {
            Scanner sc = new Scanner(new BufferedInputStream(System.in));
            int n = sc.nextInt();
            Queue<Integer> q = new PriorityQueue<>((e1, e2) -> e2 - e1);
            for (int i = 0; i < n; i++)
                q.add(sc.nextInt());
 
            while (q.size() > 1) {
                int x = q.poll(), y = q.poll();
                if (x > y)
                    q.add(x-y);
            }
            System.out.println(q.isEmpty() ? 0 : q.peek());
        }
    }
    public static void main(String[] args) throws IOException { 
        Solution s = new Solution();
        s.init();
    }
}

复杂度分析

  • 时间复杂度: O ( n l o g n ) O(nlogn)
  • 空间复杂度: O ( n ) O(n)

方法二:01 dp

我是在想不到背包,感觉这题考察的是问题的转化:微观来看,由于每次都是以两个为一组的石头进行相碰;宏观来看就是将所有石头分成两大堆进行碰撞,求如何分配才能让碰撞结果变得最小;

又由于最后最多会剩下一个石头,所以最后的结果要么是最小要么是 0,由此推出两大堆石头中质量较小的一堆的总质量 s u m 2 \leqslant \cfrac{sum}{2} ,那如何才能让两堆石头的差值最小?答案是尽量让两堆石头的重量尽量接近,所以问题转化为:背包容量为 s u m 2 \cfrac{sum}{2} 时,尽量最大化重量较小的一堆的石头的重量。

  • 定义状态
    • f [ j ] f[j] 表示背包容量为 j j 时,可收纳的最大重量的石头.
  • 思考初始化:
    • f [ 0... c a p ] = 0 f[0...cap] = 0
  • 思考状态转移方程
    • f [ i ] = m a x ( f [ i ] ,   f [ i w [ i ] ] + w [ i ] ) f[i] = max(f[i],\ f[i-w[i]] + w[i]) 对于第 i i 堆石头,要么装下,要么不装。
  • 思考输出 ( s u m f [ c a p ] ) f [ c a p ] (sum - f[cap]) - f[cap] 表示
import java.util.*;
import java.math.*;
import java.io.*;
public class Main{
    static class Solution {
        void init() {
            Scanner sc = new Scanner(new BufferedInputStream(System.in));
            int n = sc.nextInt(), sum = 0, w[] = new int[n];
            for (int i = 0; i < n; i++) {
                w[i] = sc.nextInt();
                sum += w[i];
            }
            int cap = sum >>> 1, f[] = new int[cap+1];

            for (int i = 0; i < n; i++) 
            for (int j = cap; j >= w[i]; j--) {
                f[j] = Math.max(f[j], f[j-w[i]] + w[i]);
            } 
            System.out.println(sum - f[cap] - f[cap]);
        }  
    }
    public static void main(String[] args) throws IOException { 
        Solution s = new Solution();
        s.init();
    }
}

复杂度分析

  • 时间复杂度: O ( n × c a p ) O(n × cap)
  • 空间复杂度: O ( n ) O(n)

类似的问题还有:将一对数字分割为两个集合,两个集合的最小差值(同样也是 )

猜你喜欢

转载自blog.csdn.net/qq_43539599/article/details/106801965
今日推荐