背包问题 DP
各種各樣的基礎背包
0-1 背包
非常樸素,復雜度 \(O(nV)\)
void z_o_pack(int c,int v) {for(int i=V;i>=c;i--)dp[i]=max(dp[i],dp[i-c]+v); }完全背包
復雜度 \(O(nV)\)
void comp_pack(int c,int v) {for(int i=c;i<=V;i++)dp[i]=max(dp[i],dp[i-c]+v); }多重背包
單調隊列優化
設 \(dp[i][j]\) 表示前 \(i\) 個物品放入容量為 \(j\) 的背包的最大收益 。
\[dp[i][j]=\max_{k=0}^{k\le k[i]}{\{dp[i-1][j-k\times c[i]]+k\times w[i]\}} \]考慮 \(dp\) 的轉移 。
\[0\le p < c[i],0\le j \le \left\lfloor \dfrac{V-p}{c[i]}\right\rfloor,0\le k \le k[i] \]\[dp[i][p+j\times c]=\max{\{dp[i-1][p+(j-k)\times c]+k\times w\}} \]\[dp[i][p+j\times c]=\max{\{dp[i-1][p+(j-k)\times c]-(j-k)\times w+j\times w\}} \]\[dp[i][p+j\times c]=\max{\{dp[i-1][p+(j-k)\times c]-(j-k)\times w\}}+j\times w \]這樣就可以進行單調隊列優化了 。
復雜度可以達到 \(nV\)
int ql,qr; struct QUE {int num,val; }que[Maxv]; void many_pack(int c,int w,int m) {if(!c) { add+=m*w; return; }m=min(m,V/c);for(int pos=0,s;pos<c;pos++){ql=1,qr=0,s=(V-pos)/c;for(int j=0;j<=s;j++){while(ql<=qr && que[qr].val<=(dp[pos+j*c]-j*w)) qr--;que[++qr]=(QUE){j,dp[pos+j*c]-j*w};while(ql<=qr && (j-que[ql].num)>m) ql++;dp[pos+j*c]=max(dp[pos+j*c],que[ql].val+j*w);}} }二進制分組
咕咕咕
多維限制背包
只是多加幾位,沒有太大區別。
混合三種背包的問題
for(int i=1;i<=n;i++) {if(/*是0-1背包*/) z_o_pack(c[i],w[i]);if(/*是完全背包*/) comp_pack(c[i],w[i]);if(/*是多重背包*/) many_pack(c[i],w[i],m[i]); }分組背包
同一組內只能選一個物品。
for(int i=1;i<=n;i++)for(int j=V;j>=0;j--)for(int k=1;k<=cnt[i];k++) if(j>=c[i][k])dp[j]=max(dp[j],dp[j-c[i][k]]+w[i][k]);這樣保證每一組內只會選擇一個。
很多背包問題都能都轉化為分組背包。
樹形依賴背包
復雜度:\(O(n^2)\)
雖然有三層循環,但是內層運算總量與整棵子樹內點對個數一致。
void dfs(int x,int fa) {dp[x][1]=w[i],siz[x]=1;for(int i=hea[x];i;i=nex[i]){if(ver[i]==fa) continue;dfs(ver[i],x);siz[x]+=siz[ver[i]];for(int j=min(V+1,siz[x]);j>=2;j--) // 注意要從大到小枚舉,防止后效性for(int k=1;j-k>=1;k++)dp[x][j]=max(dp[x][j],dp[x][j-k]+dp[ver[i]][k]);} }for(int i=1;i<=n;i++) if(!ind[i]) add(0,i); // 給出的圖是一個森林 dfs(0,-1); printf("%d\n",dp[0][V+1]);注意:想這一類題目時只用考慮如何設置 \(dp_{i,j}\) ?初始狀態是什么?如何轉移?
泛化物品
即一個物品的價值與消耗都會隨著加入位置、時間的改變而改變。
注意:既然物品的價值隨著加入位置而改變,應該現將物品排序再加入。
不同問法的背包問題
輸出方案
記錄從哪一項轉移過來即可
求字典序最小的方案
咕咕咕
求方案總數
將 \(\max\) 改為 \(sum\) ,其余不變(此時求出的方案數,包括物品總價不是最大的情況)
求最優方案總數
咕咕咕
求次優解、第K優解
咕咕咕
常見例題:
多種背包混合
P1941 飛揚的小鳥
$\texttt{solution}$算法:\(01\) 背包 \(+\) 完全背包 。
狀態:設 \(dp[i][j]\) 表示橫坐標為 \(i\) 高度為 \(j\) 的最少點擊次數 。
上升 :完全背包 轉移方式
下降 :\(01\) 背包 轉移方式
超過 \(m\) 變為 \(m\) : 特判
代碼:
for(int i=1;i<=n;i++) Low[i]=1,High[i]=m; for(int i=1;i<=k;i++) p=rd(),e[p]=1,Low[p]=rd()+1,High[p]=rd()-1; memset(g,inf,sizeof(g)); for(int i=1;i<=m;i++) g[i]=0; for(int i=1;i<=n;i++) {memset(f,inf,sizeof(f));for(int j=x[i];j<=x[i]+m;j++) f[j]=min(f[j-x[i]]+1,g[j-x[i]]+1); // 完全背包for(int j=m+1;j<=m+x[i];j++) f[m]=min(f[j],f[m]); // 特判超過 mfor(int j=1;j<=m-y[i];j++) f[j]=min(f[j],g[j+y[i]]); // 01 背包for(int j=1;j<Low[i];j++) f[j]=inf;for(int j=High[i]+1;j<=m;j++) f[j]=inf; // 不可行方案if(e[i]) for(int j=Low[i];j<=High[i];j++) if(f[j]<inf) { cnt++; break; } // 統計通過的管道數memcpy(g,f,sizeof(g)); } for(int i=1;i<=m;i++) ans=min(ans,g[i]); if(ans!=inf) printf("1\n%d\n",ans); else printf("0\n%d\n",cnt);合并兩個限制條件相同的背包
P4095 [HEOI2013]Eden 的新背包問題
題意:給定 \(n\) 種物品,每種有 \(m_i\) 件,每一件價值為 \(v_i\) ,重量為 \(c_i\) 。由于某些原因,在第 \(i\) 個詢問中,第 \(i\) 種物品不能選擇,請對于每個詢問求出當前條件下的最大收益。
其中, \(n\le 1000,c_i\le 100\) 。
考慮從前往后、從后往前進行背包。
在第 \(i\) 個詢問中合并 \(pre[i-1]\) 與 \(suf[i+1]\) 的背包。
for(int j=0;j<=v;j++) ans=max(ans,dp[0][num-1][j]+dp[1][num+1][v-j]);當組內重量、價值連續時分組背包的前綴和優化
CF1559E Mocha and Stars
題意:
求有多少長 \(n\) 的序列 \(a\) 滿足:
- \(l_i\le a_i\le r_i\)
- \(\sum_{i=1}^{n}a_i\le m\)
- \(\gcd(a_1,\dots,a_n)=1\)
答案對 \(998244353\) 取模。
設 \(f(a_1,a_2,a_3,\dots,a_n)\) 是否是一個滿足前兩個條件的序列。
\[\begin{aligned}ans & = \sum\limits_{a_1=l_1}^{r_1}\sum\limits_{a_2=l_2}^{r_2}...\sum\limits_{a_n=l_n}^{r_n}f(a_1,a_2,...,a_n)[\gcd(a_1,a_2,...,a_n)=1] \\ & = \sum\limits_{a_1=l_1}^{r_1}\sum\limits_{a_2=l_2}^{r_2}...\sum\limits_{a_n=l_n}^{r_n}f(a_1,a_2,...,a_n)\sum\limits_{d\mid \gcd(a_1,a_2,...,a_n)}\mu(d)\\ &= \sum\limits_{a_1=l_1}^{r_1}\sum\limits_{a_2=l_2}^{r_2}...\sum\limits_{a_n=l_n}^{r_n}f(a_1,a_2,...,a_n)\sum\limits_{d\mid a_1 \& d\mid a_2 \& ... \& d\mid a_n}\mu(d) \\&= \sum\limits_{d=1}^m\mu(d) \sum\limits_{a_1=\left\lceil \frac{l_1}ze8trgl8bvbq\right \rceil}^{\left\lfloor \frac{r_1}ze8trgl8bvbq\right\rfloor}\sum\limits_{a_2\left\lceil \frac{l_2}ze8trgl8bvbq\right \rceil}^{\left\lfloor \frac{r_2}ze8trgl8bvbq\right\rfloor}...\sum\limits_{a_n=\left\lceil \frac{l_n}ze8trgl8bvbq\right \rceil}^{\left\lfloor \frac{r_n}ze8trgl8bvbq\right\rfloor}f(a_1,a_2,...,a_n)\end{aligned} \]之后相當于一個分組背包,有 \(n\) 件物品,每一件都在 \([l_i,r_i]\) 之間,去除不超過 \(m\) 的重量,求方案數。
$\texttt{code}$ #include<bits/stdc++.h> using namespace std; #define infll 0x7f7f7f7f7f7f7f7fll #define inf 0x3f3f3f3f #define Maxn 55 #define Maxm 100005 #define mod 998244353 #define int long long typedef long long ll; inline int rd() {int x=0;char ch,t=0;while(!isdigit(ch = getchar())) t|=ch=='-';while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();return x=t?-x:x; } ll maxll(ll x,ll y){ return x>y?x:y; } ll minll(ll x,ll y){ return x<y?x:y; } ll absll(ll x){ return x>0ll?x:-x; } ll gcd(ll x,ll y){ return (y==0)?x:gcd(y,x%y); } int n,m,tot; int L[Maxn],R[Maxn],l[Maxn],r[Maxn]; int dp[Maxm],tmp[Maxm],pri[Maxm],mu[Maxm]; ll ans; bool vis[Maxm]; signed main() {//ios::sync_with_stdio(false); cin.tie(0);//freopen(".in","r",stdin);//freopen(".out","w",stdout);n=rd(),m=rd();for(int i=1;i<=n;i++) L[i]=rd(),R[i]=rd();vis[1]=mu[1]=true;for(int i=2;i<Maxm;i++){if(!vis[i]) mu[i]=-1,pri[++tot]=i;for(int j=1;j<=tot && i*pri[j]<Maxm;j++){vis[i*pri[j]]=true;if(i%pri[j]==0) break;mu[i*pri[j]]=-mu[i];}}for(int d=1,MAX;d<=m;d++) if(mu[d]){MAX=m/d;for(int i=1;i<=n;i++) l[i]=(L[i]+d-1)/d,r[i]=R[i]/d;for(int i=0;i<=MAX;i++) dp[i]=1;for(int i=1;i<=n;i++){for(int j=0;j<=MAX;j++) tmp[j]=0;for(int j=l[i];j<=MAX;j++){tmp[j]=dp[j-l[i]];if(j>r[i]) tmp[j]=(tmp[j]-dp[j-r[i]-1]+mod)%mod;}dp[0]=0;for(int j=1;j<=MAX;j++) dp[j]=(dp[j-1]+tmp[j])%mod;}ans=(ans+1ll*dp[MAX]*mu[d]%mod+mod)%mod;}printf("%d\n",ans);//fclose(stdin);//fclose(stdout);return 0; }一分為二
P1651 塔
題意:有 \(n\) 個數,從中選出兩個集合(可以有剩余),使兩個數集中的數字之和相等。
!!!這不是一道背包問題,因為可以有剩余,所以更具判斷 \(dp[sum]\ne -1\) 和 \(dp[2\times sum]\ne -1\) 是錯的。
選出的 \(2\times sum\) 和 \(sum\) 不一定是包含關系。
應該用普通 \(\text{DP}\) 解決。
(當然,當 \(n\) 非常小的時候,可以直接使用 meet in the middle 解決問題) 如:P3067 [USACO12OPEN]Balanced Cow Subsets G
兩種限制/收益的問題
將數組下標的一位設為 “任務一” 的值,而將數組內容設為 “任務二” 的最小 \(/\) 最大值。
輸出答案的時候將下標與值取和、最小、最大……
P2224 [HNOI2001]產品加工
\(\rightarrow\) P2224 solution
用 \(dp[i][j]\) 表示加到 \(i\) 為止,機器 \(1\) 使用了 \(j\) 的時間,而 \(dp[i][j]\) 值表示機器 \(2\) 消耗的時間。
跑背包。
n=rd(); memset(dp,inf,sizeof(dp)),dp[0][0]=0; for(int i=1,x,y,z,opt,pre;i<=n;i++) {x=rd(),y=rd(),z=rd(),opt=i&1,pre=opt^1;MAX+=max(x,max(y,z));for(register int j=0;j<=MAX;j++) dp[opt][j]=inf; // 防止上一次的值影響for(register int j=0;j<=MAX;j++){if(y) dp[opt][j]=min(dp[opt][j],dp[pre][j]+y);if(j>=x && x) dp[opt][j]=min(dp[opt][j],dp[pre][j-x]);if(j>=z && z) dp[opt][j]=min(dp[opt][j],dp[pre][j-z]+z);} } for(int i=0;i<=MAX;i++) ans=min(ans,max(dp[n&1][i],i)); printf("%d\n",ans);P2340 [USACO03FALL]Cow Exhibition G
題意:有 \(n\) 頭奶牛,每一頭奶牛有一個情商值和智商值,現在要從中選出若干頭奶牛。在奶牛的情商和與智商和都不小于 \(0\) 的情況下,求出情商與智商之和的最大值。
(需要滾動數組)設 \(dp[j]\) 表示在前 \(i\) 頭奶牛中選出情商為 \(j\) 時的智商最大值為 \(dp[j]\) 。
暴力轉移。
最后在 \(i>0\) 的情況下,計算 \(i+dp[i]\) 的最大值。
比較復雜的題目
P3891 [GDOI2014]采集資源
先進行一次 \(\text{DP}\) 求出 \(dp1[i]\) 數組,表示花費 \(i\) 能夠獲得的最大勞動力。
之后進行第二次 \(\text{DP}\) 求出 \(dp2[i][j]\) ,表示在第 \(i\) 時間花費 \(j\) 能獲得最大勞動力(單位時間內的收獲最多)。轉移方程:
\[dp[i+1][j-k+dp1[k]+dp2[i][j]]=\max(dp[i+1][j-k+dp1[k]+dp2[i][j]],dp[k]+dp[i][j]) \]在求出 \(dp2\) 的同時比較此時是否能使收益 \((j-k+dp[k]+dp[i][j])\) 大于 \(t\) ,及時輸出。
注意:初始化使需將數組賦值為不可能取到的值,以防錯誤轉移。
P2481 [SDOI2010]代碼拍賣會
對于任何一個豬豬舉牌的方案,都可以看做 \(9\) 個以內的若干 “\(1\) 的后綴” 相加而成!
我們可以把一個數分割成若干個 \(000000\dots 11111\) 的和。
不妨記 \(cnt(i)\) 為 \(\pmod p\) 意義下 \([\text{余數是 i 的“1 的后綴”}]\) 的數量。
之后完成一個背包就好了。
設 \(dp(i,j,k)\) 表示當前考慮到余數為 \(i\) 的 “\(1\) 的后綴” ,此前已經放上了 \(j\) 個 “\(1\) 的后綴” ,此時構成的數字的 \(\pmod p\) 的余數是 \(k\) 的方案數。
初始化:全部填 \(1\) 的 \(n\) 位數對 \(p\) 取模后的狀態。(注意這里的處理!!!就是這里卡了 \(4~Hours\) )
設:
- \(put\) 表示放入 \(put\) 個余數為 \(i\) 的 “\(1\) 的后綴” 。
- \(Left\) 表示放入前已經有了 \(Left\) 的余數。
- \(d\) 表示在放入前已經有 \(s\) 個數了。
- 二項式表示從所有余數為 \(i\) 的 “\(1\) 的后綴” 取出 \(put\) 個的方案數(證明見數論基礎)。
轉移方程:
\[dp(i,s+put,(Left+i\times put)\%p)+=\dbinom{cnt(i)+put-1}{put}\times \sum\limits dp(i-1,s,Left) \] $\texttt{code}$ #define Maxp 505 #define mod 999911659 ll n,p,len,add; ll cnt[Maxp],Last_pos[Maxp],inv[11]; ll f[11][Maxp],g[11][Maxp]; ll C(ll x,ll y) {ll ret=1;for(ll i=x;i>=x-y+1;i--) ret=ret*i%mod;for(ll i=y;i>=2;i--) ret=ret*inv[i]%mod;return ret; } void pre_cnt() {ll st,addn;for(ll i=1,tmp=0;i<=p+p;i++){tmp=(tmp*10+1)%p;if(Last_pos[tmp]) { st=Last_pos[tmp]-1,len=i-Last_pos[tmp]; break; }addn=tmp,Last_pos[tmp]=i; // 這里!!! }if(n<st) for(int i=1,tmp=0;i<=n;i++) tmp=(tmp*10+1)%p,cnt[tmp]++,addn=tmp;else{ll Times=(n-st)/len,ed=(n-st)%len+st;if((n-st)%len==0) ed=0; // 這里!!! for(int i=1,tmp=0;i<=st+len;i++){tmp=(tmp*10+1)%p;if(i==ed) addn=tmp; // 這里!!! if(i<=st) cnt[tmp]++;else{cnt[tmp]=(cnt[tmp]+Times)%mod;if(i<=ed) cnt[tmp]=(cnt[tmp]+1)%mod;}}}f[0][addn]=1; } ll Dp() {for(int i=0;i<p;i++){memcpy(g,f,sizeof(f)),memset(f,0,sizeof(f));for(int s=0;s<9;s++) for(int Put=0;s+Put<9;Put++){ll mul=C(cnt[i]+Put-1,Put);for(int Left=0;Left<p;Left++)f[s+Put][(Left+i*Put)%p]=(f[s+Put][(Left+i*Put)%p]+mul*g[s][Left]%mod)%mod;}}ll ret=0;for(int i=0;i<9;i++) ret=(ret+f[i][0])%mod;return ret; } int main() {scanf("%lld%lld",&n,&p);inv[0]=inv[1]=1; for(ll i=2;i<=10;i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod;pre_cnt();printf("%lld\n",Dp());return 0; }總結
- 上一篇: 360 与航天宏图达成战略合作:在气象、
- 下一篇: vivo Y 系列手机中国市场用户突破