【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(nlogn)O(nlogn)
  • 空間複雜度:O(n)O(n)

方法二:01 dp

我是在想不到揹包,感覺這題考察的是問題的轉化:微觀來看,由於每次都是以兩個爲一組的石頭進行相碰;宏觀來看就是將所有石頭分成兩大堆進行碰撞,求如何分配才能讓碰撞結果變得最小;

又由於最後最多會剩下一個石頭,所以最後的結果要麼是最小要麼是 0,由此推出兩大堆石頭中質量較小的一堆的總質量 sum2\leqslant \cfrac{sum}{2},那如何才能讓兩堆石頭的差值最小?答案是儘量讓兩堆石頭的重量儘量接近,所以問題轉化爲:揹包容量爲 sum2\cfrac{sum}{2} 時,儘量最大化重量較小的一堆的石頭的重量。

  • 定義狀態
    • f[j]f[j] 表示揹包容量爲 jj 時,可收納的最大重量的石頭.
  • 思考初始化:
    • f[0...cap]=0f[0...cap] = 0
  • 思考狀態轉移方程
    • f[i]=max(f[i], f[iw[i]]+w[i])f[i] = max(f[i],\ f[i-w[i]] + w[i]) 對於第 ii 堆石頭,要麼裝下,要麼不裝。
  • 思考輸出(sumf[cap])f[cap](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×cap)O(n × cap)
  • 空間複雜度:O(n)O(n)

類似的問題還有:將一對數字分割爲兩個集合,兩個集合的最小差值(同樣也是 )

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章