1. 程式人生 > >舊題再做【bzoj2287】【[pojchallenge]消失之物】分治揹包

舊題再做【bzoj2287】【[pojchallenge]消失之物】分治揹包

這裡寫圖片描述
(上不了p站我要死了)

今天聽了 doggu神 講了這道題的另一種做法,真是腦洞大開、眼界大開。雖然複雜度比黃學長的要大一點,但不總結一下簡直對不起這神思路。
方法1:黃學長的做法(點這裡)

Description
ftiasch 有 N 個物品, 體積分別是 W1, W2, …, WN。 由於她的疏忽, 第 i 個物品丟失了。 “要使用剩下的 N - 1 物品裝滿容積為 x 的揹包,有幾種方法呢?” – 這是經典的問題了。她把答案記為 Count(i, x) ,想要得到所有1 <= i <= N, 1 <= x <= M的 Count(i, x) 表格。
Input


第1行:兩個整數 N (1 ≤ N ≤ 2 × 103) 和 M (1 ≤ M ≤ 2 × 103),物品的數量和最大的容積。
第2行: N 個整數 W1, W2, …, WN, 物品的體積。
Output
一個 N × M 的矩陣, Count(i, x)的末位數字。
Sample Input
3 2
1 1 2
Sample Output
11
11
21
HINT
如果物品3丟失的話,只有一種方法裝滿容量是2的揹包,即選擇物品1和物品2。

這道題最開始會有兩種想法:1、用總方案數減去用該物品的方案數,這個的分支的最終思路就是黃學長的做法。 2、去掉每一個物品,重新跑揹包(這不是暴力嗎喂?!)

假設我們沒有想到 方法1,那該怎麼辦呢?我們可以很直觀的感覺到:去掉i,我們要用1~i-1和i+1~n個物品去更新揹包;去掉j,我們要用1~j-1和j+1~n個物品去更新揹包。
其中大部分都是一樣的,我們會直觀的感覺到這是冗餘部分,是不需要這麼多次的,希望可以減少無用功。

首先,揹包有個性質:物品更新的先後順序並不影響最終結果,這是肯定的。

我們想:如果去掉 i、j,用剩餘的n-2個物品去更新。再用 j 更新,就得到了去掉 i 的答案;同理,再用 i 去更新,就得到了 j 答案。對於多個物品的塊,亦然。

那麼我們是否就想到了分治?把物品分為上述的塊,再來解決?
那麼對於當前的分治狀態solve(int le,int ri),此時我們已經用1~le-1和ri+1~n的物品將揹包處理好了,直接處理該塊沒有意義,因為我們還可以將塊分下去。將[le,ri]分為兩段,如果要處理[le,mid],就用mid+1~ri的物品將之前更新好了的揹包再更新一遍;同理,處理[mid+1,ri],就用le~mid的物品去更新之前的揹包。
就這樣遞迴下去,直到le==ri,這時的揹包就是對於去掉le的答案了,輸出即可。

因為是這是中序遍歷,一共有log層,所以只需開log個揹包即可

複雜度:因為每一層都要跑揹包,共log層,所以總時間複雜度o(nmlogn),空間複雜度o(mlogn)

程式碼:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=2000+5;

int n,m,w[N];
int f[15][N];

void solve(int dep,int le,int ri){
    if(le==ri){
        for(int i=1;i<=m;i++) printf("%d",f[dep][i]%10);
        printf("\n");
        return;
    }
    int mid=(le+ri)>>1;
    for(int j=0;j<=m;j++) f[dep+1][j]=f[dep][j];
    for(int i=mid+1;i<=ri;i++)
        for(int j=m;j>=w[i];j--)
            f[dep+1][j]+=f[dep+1][j-w[i]],f[dep+1][j]%=10;//some items have been done
    solve(dep+1,le,mid);
    for(int j=0;j<=m;j++) f[dep+1][j]=f[dep][j];
    for(int i=le;i<=mid;i++)
        for(int j=m;j>=w[i];j--)
            f[dep+1][j]+=f[dep+1][j-w[i]],f[dep+1][j]%=10;
    solve(dep+1,mid+1,ri);
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&w[i]);
    f[0][0]=1;
    solve(0,1,n);
    return 0;
}

就算不是最優的,但這思路實在是巧。提高自己的眼界總歸是一件好事。