消失之物「分治」
阿新 • • 發佈:2020-09-04
消失之物「分治」
題目描述
ftiasch 有 \(N\) 個物品, 體積分別是 \(W_1, W_2, ..., W_N\)。 由於她的疏忽, 第 \(i\) 個物品丟失了。 “要使用剩下的 \(N - 1\) 物品裝滿容積為 \(x\) 的揹包,有幾種方法呢?” -- 這是經典的問題了。她把答案記為 \(Count(i, x)\) ,想要得到所有 \(1 <= i <= N, 1 <= x <= M\) 的 \(Count(i, x)\) 表格。
輸入格式
第 \(1\) 行:兩個整數 \(N (1 ≤ N ≤ 2 × 10^3)\) 和 \(M (1 ≤ M ≤ 2 × 10^3)\)
第 \(2\) 行: \(N\) 個整數 \(W_1, W_2, ..., W_N\), 物品的體積。
輸出格式
一個 \(N × M\) 的矩陣, \(Count(i, x)\) 的末位數字
樣例
樣例輸入
3 2
1 1 2
樣例輸出
11
11
21
思路分析
- 直接暴力跑一個揹包能拿70~80分(
甚至完全可以用揹包水過) - 不難發現,在每一次更新不能拿的物品時,其它可以拿的是有大量的重複計算的,所以考慮將其保留,直接使用
- 這時候就可以用分治了,每次把一個區間分成兩部分,一部分不動,另一部分去選不能拿的
- 另外要注意,每一次更新答案之前要撤銷對另一半答案的更新
- 最後會分成一個長得像線段樹的東西,每個相當於葉子結點的就代表該點不能拿,一共有 \(log(n)\)
詳見程式碼
Code
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #define N 20200 #define R register using namespace std; inline int read(){ int x = 0,f = 1; char ch = getchar(); while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } int n,m,a[N],f[20][N]; void solve(int l,int r,int dep){ //dep表示當前分治樹的深度 if(l==r){ for(R int i = 1;i <= m;i++)printf("%d",f[dep][i]); puts(""); return; } int mid = (l+r)>>1; for(R int i = m;i >= 0;i--)f[dep+1][i] = f[dep][i]; for(R int i = mid+1;i <= r;i++){ //右半部分全部都能拿,直接更新即可 for(R int j = m;j >= 0;j--){ f[dep+1][j] = (f[dep+1][j]+f[dep+1][j-a[i]])%10; } } solve(l,mid,dep+1); //左半部分有不能拿的,遞迴解決 for(R int i = m;i >= 0;i--)f[dep+1][i] = f[dep][i]; //反過來再處理一下,在此之前要撤銷 for(R int i = l;i <= mid;i++){ for(R int j = m;j >= 0;j--){ f[dep+1][j] = (f[dep+1][j]+f[dep+1][j-a[i]])%10; } } solve(mid+1,r,dep+1);//同上 } int main(){ n = read(),m = read(); for(R int i = 1;i <= n;i++)a[i] = read(); f[0][0] = 1; solve(1,n,0); return 0; }