完全背包問題 解題報告


完全背包問題

\(n\)種物品,物品的體積分別為\(V_1,V_2,\dots,V_n\),且每種物品的數量都可以看做是無限多的。現在有\(m\)次詢問,每次詢問給定一個容量為取的背包,請你回答是否存在一種物品選擇方案,使得背包恰好能被完全裝滿(僅考慮體積,忽略長、寬、高等其他因素)。同時,要求所有選出的物品中,體積不小於\(L\)的物品總數量不能超過\(C\)件。

輸入格式

第一行為兩個正整數\(n\)\(m\),分別表示物品的種數以及詢問的次數。
第二行為\(n\)個正整數\(V_1,V_2,\dots,V_n(V_i \le 10000)\),分別表示這\(n\)種物品的體積。
第三行為兩個非負整數 \(L(L \le 20000 )\)\(C(C\le 30)\),表示在選擇方案中對大體積物品的數量限制。
接下來\(m\)行,每行一個正整數\(W_i\),表示這次詢問中背包的容量。

輸出格式

輸出共\(m\)行,每行一個字符串,表示對應詢問的答案。
對於每次詢問,如果存在一種合法的方案,請輸出\(Yes\),否則輸出\(No\)

數據規模與約定

對於\(10\%\)的數據:\(n\le 8,W_i\le 100\)

對於\(30\%\)的數據:\(W_i\le 10000\)

對於\(60\%\)的數據:\(n\le 30,m\le 200\)

對於另外\(10\%\)的數據:\(n=2\)

對於\(100\%\)的數據:\(n\le 50,m\le 100000,W_i\le 10^{18}\)


考慮暴力

\(dp_{i,j,k}\)表前\(i\)個物品總體積為\(k\)選擇了\(j\)件大物品的是否合法。

復雜度\(O(n\max W_i c)\)

考慮修補這個想法。設\(V_0\)為最小的體積

  • 如果\(V_0\ge L\),因為可選的物品總體積不會太大,所以考慮直接暴力,復雜度\(O(nc^2\sum V_i)\),用\(\tt{bitset}\)

優化一下就可以過了。

  • 如果\(V_0 < L\),因為\(V_0\)可以無限選,所以我們不妨求出在同余於\(V_0\)情況下的可能,這樣就簡化了狀態。

\(dp_{i,j,k}\)表示前\(i\)個物品選擇了\(j\)件大物品選擇的總體積同余於\(V_0\)下為\(k\)的最小總體積。

保證了最小總體積,我們就可以判斷\(W_i\)是否大於等於\(dp_{n,\forall j\le c,W_i \bmod V_0}\)來看是否合法

考慮轉移

\(dp_{i,j,k}=\min(dp_{i,j,(v_0-v_i)\bmod v_0}+v_i,dp_{i-1,j,k}),v_i < L\)

\(dp_{i,j,k}=\min(dp_{i,j-1,(v_0-v_i)\bmod v_0}+v_i,dp_{i-1,j,k}),v_i \ge L\)

發現第一個轉移有環,可以建圖解決。

源點\(S\)連接所有\(0 \sim v_0-1\)的點,邊權為\(dp_{i,j,v_0}\),然后其他點每個點\(v\)\((v+v_i)\bmod v_0\),邊權為\(v_i\),然后跑一邊最短路就可以了。

發現這個圖比較特殊,可以通過尋找性質\(O(n)\)解決。


Code:

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <bitset>
#define ll long long
int n,c,m,v[52],L;ll w;
int min(int x,int y){return x<y?x:y;}
namespace work1
{
std::bitset <1000001> dp[31];
void work()
{
    dp[0][0]=1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=c;j++)
            dp[j]|=dp[j-1]<<v[i];
    for(int i=1;i<=m;i++)
    {
        scanf("%lld",w);
        int ans=0;
        for(int j=0;j<=n;j++) ans|=dp[j][w];
        if(ans) puts("Yes");
        else puts("No");
    }
}
}
namespace work2
{
const int N=10010;
const int inf=0x3f3f3f3f;
int dp[31][N],used[32][N],mi,id;
#define dec(a,b) (((a-b)%v[1]+v[1])%v[1])
#define to ((now+v[p])%v[1])
void dfs0(int p,int k,int now)
{
    if(used[k][now]) return;
    used[k][now]=1;
    if(mi>dp[k][now]) mi=dp[k][now],id=now;
    dfs0(p,k,to);
}
void dfs1(int d,int p,int k,int now,int anc)
{
    if(now==anc) return;
    dp[k][now]=min(dp[k][now],d+v[p]);
    dfs1(dp[k][now],p,k,to,anc);
}
void topo(int p)
{
    memset(used,0,sizeof(used));
    for(int i=0;i<=c;i++)
        for(int j=0;j<v[1];j++)
            if(!used[i][j])
            {
                mi=inf;
                dfs0(p,i,j);
                dfs1(dp[i][id],p,i,(id+v[p])%v[1],id);
            }
}
void work()
{
    memset(dp,0x3f,sizeof(dp));
    dp[0][0]=0;
    for(int i=2;i<=n;i++)
    {
        if(v[i]<L) topo(i);
        else
        {
            for(int k=0;k<=c;k++)
                for(int j=0;j<v[1];j++)
                    dp[k][j]=min(k?dp[k-1][dec(j,v[i])]+v[i]:inf,dp[k][j]);
        }
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%lld",&w);
        int ans=0;
        for(int j=0;j<=c;j++)
            if(dp[j][w%v[1]]!=inf)
                ans|=dp[j][w%v[1]]<=w;
        if(ans) puts("Yes");
        else puts("No");
    }
}
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",v+i);
    scanf("%d%d",&L,&c);
    std::sort(v+1,v+1+n);
    if(v[1]>=L) work1::work();
    else work2::work();
    return 0;
}

2018.10.30


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM