记录结果再利用的“动态规划”
动态规划重点一是状态表示和集合的划分(思维的难点),二是边界问题的处理,三是巧妙利用递推式的形式,考虑计算的顺序或者数据结构进行优化(代码实现中的难点)。
状态表示需要题感,自己去试试,集合划分则需要寻找第一个不同的状态进行划分。
书上的问题:
01背包问题
超超有名的背包问题,白书上的循序渐进的方法可以让我们对记忆化搜索和dp的理解更深刻。dp到底是怎么打表的?
书上先谈了一个朴素的暴力做法:
int dfs(int i,int j)
{
if(i==n)//已经没有剩余物品了
res=0;
else if(j<w[i])//无法挑选这个物品
res=dfs(i+1,j);
else//可以挑选也可以不挑选,选最大值
res=max(dfs(i+1,j),dfs(i+1,j-w[i])+v[i]);
return res;
}
由于递归中,很多重复使用的dfs状态,我们用一个dp数组来保存它,就得到了以下记忆化搜索的方式:
int dfs(int i,int j)
{
if(dp[i][j])
return dp[i][j];
if(i==n)//已经没有剩余物品了
res=0;
else if(j<w[i])//无法挑选这个物品
res=dfs(i+1,j);
else//可以挑选也可以不挑选,选最大值
res=max(dfs(i+1,j),dfs(i+1,j-v[i])+w[i]);
return dp[i][j]=res;
}
注意,如果把价值之和sum写在参数上,不记录在res中,反而会难以记忆化搜索。
用dp可以写成:
for(int i=1;i<=n;i++)
{
for(int j=0;j<=W;j++)
{
if(j<w[i])
{
f[i][j]=f[i-1][j];
}
else
{
f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i];
}
}
}
dp问题的滚动数组再利用
我们发现i都是由i-1推来的,所以我们用一维数组就可以写01背包啦。
而j为什么要反向推呢?因为反向推的时候,dp数组更新是从大数更到小数的,这样大数变成i时候的值,小数还是i-1时候的值,不会影响到递推。
for(int i=1;i<=n;i++)
{
for(int j=W;j>=w[i];j--)
{
f[j]=max(f[j],f[j-w[i]]+v[i]);
}
}
根据背包九讲
我们还有如下问题:(摘自acwing的提高课)
- 完全背包问题
- 混合背包问题
- 多重背包问题
- 二维费用背包问题
- 分组背包问题
- 有依赖的背包问题
- 背包问题求方案数
- 背包问题求具体方案
限于篇幅,在这里只提一下完全背包和多重背包。
完全背包问题
如果我们递推只需要用到这一层和上一层的状态,我们可以用滚动数组优化,隔两层就会覆盖掉,节省了空间。一般采用f[i&1][j]的方式存储,在书上p60页的例子,我们用滚动数组优化完全背包问题:
void solve()
{
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
for(int k=0;k*v[i]<=j;k++)
{
dp[i][j]=max(dp[i][j],dp[i][j-k*v[i]]+k*w[i]);
}
}
}
}
朴素的算法是O(nm2)的,显然不够优秀。
dp[i][j]=max(dp[i][j],dp[i][j-v]+w,…dp[i][j-k*v]+kw…)
dp[i][j-v]=max(dp[i][j-v],…,dp[i][j-kv]+kw…)
因此
dp[i][j]=max(dp[i][j],dp[i][j-v])
如果我们把它优化成一维,需要正向递推体积,因为这里更新dp[i][j]的是dp[i][j-v],要先覆盖掉i-1个物品的选取信息再更新。
#include<iostream>
using namespace std;
const int N=100010;
int v[N],w[N],a[N];
int main()
{
int n,V;
cin>>n>>V;
for(int i=0;i<n;i++)
{
cin>>v[i]>>w[i];
}
for(int i=0;i<n;i++)
for(int j=v[i];j<=V;j++)
{
a[j]=max(a[j],a[j-v[i]]+w[i]);
}
cout<<a[V];
return 0;
}
多重背包问题
#include<iostream>
using namespace std;
const int N=1010;
int f[N];
int main()
{
int n,V;
cin>>n>>V;
for(int i=0;i<n;i++)
{
int v,w,s;
cin>>v>>w>>s;
for(int j=V;j>=v;j--)
{
for(int k=1;k*v<=j&&k<=s;k++)
{
f[j]=max(f[j],f[j-v*k]+w*k);
}
}
}
cout<<f[V];
return 0;
}
多重背包问题有两种优化方式
103数量级,用二进制优化
#include<bits/stdc++.h>
using namespace std;
const int N=25000;
int v[N],w[N],f[N];
int cnt=0;
int main()
{
int N,V;
cin>>N>>V;
for(int i=0;i<N;i++)
{
int a,b,s;
cin>>a>>b>>s;
int k=1;
while(k<=s)
{
cnt++;
v[cnt]=k*a;
w[cnt]=k*b;
s-=k;
k*=2;
}
if(s>0)
{
cnt++;
v[cnt]=a*s;
w[cnt]=b*s;
}
}
for(int i=1;i<=cnt;i++)
{
for(int j=V;j>=v[i];j--)
{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
cout<<f[V];
return 0;
}
104用单调队列优化
用单调队列优化的理由是,每一次多重背包,相当于前sv个(s是每一种物品的数量)体积状态转移而来,而这就变成了一个滑动窗口求最大值问题,但是最后得到的结果值还需要加上sw。
这里队列用数组模拟,用deque过不了
#include <cstring>
#include <iostream>
#include <algorithm>
#include <deque>
using namespace std;
const int N = 20010;
int n, m;
int f[N], g[N], q[N];
deque<int> d;
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i ++ )
{
int v, w, s;
cin >> v >> w >> s;
memcpy(g, f, sizeof f);
for (int j = 0; j < v; j ++ )
{
int hh=0,tt=-1;
for (int k = j; k <= m; k += v)
{
if (hh <= tt && q[hh] < k - s * v) hh ++ ;
while (hh <= tt && g[q[tt]] - (q[tt] - j) / v * w <= g[k] - (k - j) / v * w) tt -- ;
q[ ++ tt] = k;
f[k] = g[q[hh]] + (k - q[hh]) / v * w;
/*f[k]=g[k];
if(d.size() && d.front() < k - s * v)
d.pop_front();
while (d.size() && g[d.back()] - (d.back() - j) / v * w <= g[k] - (k - j) / v * w)
d.pop_back();
d.push_back(k);
if(d.size())
f[k]=max(f[k],g[d.front()] + (k - d.front()) / v * w);*/
}
}
}
cout << f[m] << endl;
return 0;
}
二维费用背包问题
#include<iostream>
using namespace std;
const int N=1010;
int f[N][N];
int main()
{
int n,V,M;
cin>>n>>V>>M;
for(int i=0;i<n;i++)
{
int v,m,w;
cin>>v>>m>>w;
for(int i=V;i>=v;i--)
{
for(int j=M;j>=m;j--)
{
f[i][j]=max(f[i][j],f[i-v][j-m]+w);
}
}
}
cout<<f[V][M];
return 0;
}
分组背包问题
i和j表示的是维度
而k表示的是可选决策方案
所以三者的顺序绝对不能混淆。
就像floyd的k要在外层循环,是因为三维变量优化成了二维一样。
#include<iostream>
using namespace std;
const int N=1010;
int f[N];
int v[N];
int w[N];
int main()
{
int n,V;
cin>>n>>V;
for(int i=0;i<n;i++)
{
int s;
cin>>s;
for(int j=1;j<=s;j++)
{
cin>>v[j]>>w[j];
}
for(int k=V;k>=0;k--)
for(int j=1;j<=s;j++)
{
if(k>=v[j])
f[k]=max(f[k],f[k-v[j]]+w[j]);
}
}
cout<<f[V];
return 0;
}
有依赖的背包问题
由于物品之间有依赖,我们把每个依赖连成一条边进行树形dp。
把一个结点u的按子树分类,每个子树又按照子树能表示的体积继续划分。
在最后把这个结点u塞进背包,如果放不下了,则整个子树都没有价值。
因为没初始化h数组调试了好久!千万别粗心了,写数组模拟邻接表h数组必须初始化为-1。
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int f[N][N];
int h[N],e[N],ne[N],idx;
int w[N],v[N];
void add(int a,int b)
{
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
int n,V;
void dfs(int u)
{
for(int i=h[u];~i;i=ne[i])
{
dfs(e[i]);
for(int j=V-v[u];j>=v[e[i]];j--)//每个e[i]都组成一个分组,分组中的每个物品体积是下一层所能组成的体积
{
for(int k=0;k<=j;k++)
{
f[u][j]=max(f[u][j],f[u][j-k]+f[e[i]][k]);
}
}
}
for(int i=V;i>=v[u];i--)//01背包,把u放进去
{
f[u][i]=f[u][i-v[u]]+w[u];
}
for(int i=0;i<v[u];i++)//u放不进去,u的子结点也不能放了
{
f[u][i]=0;
}
}
int main()
{
cin>>n>>V;
int root;
memset(h,-1,sizeof h);//你写数组模拟邻接表必须写这个啊
for(int i=1;i<=n;i++)
{
int p;
cin>>v[i]>>w[i]>>p;
if(p==-1) root=i;
else
{
add(p,i);
}
}
dfs(root);
cout<<f[root][V];
return 0;
}
背包问题求方案数
更新的时候用一个cnt记录方案数
#include<iostream>
using namespace std;
const int N=10010;
int cnt[N];
int f[N];
int mod=1000000007;
int main()
{
int n,V;
cin>>n>>V;
for(int i=0;i<V;i++)
{
cnt[i]=1;
}
for(int i=0;i<n;i++)
{
int v,w;
cin>>v>>w;
for(int j=V;j>=v;j--)
{
int value=f[j-v]+w;
if(value>f[j])
{
f[j]=value;
cnt[j]=cnt[j-v];
}
else if(value==f[j])
{
cnt[j]=(cnt[j]+cnt[j-v])%mod;
}
}
}
cout<<cnt[V];
return 0;
}
背包问题求具体方案
在最后倒推即可
#include<iostream>
using namespace std;
const int N=1010;
int f[N][N],v[N],w[N];
int main()
{
int n,V;
cin>>n>>V;
for(int i=1;i<=n;i++)
{
cin>>v[i]>>w[i];
}
for(int i=n;i>=1;i--)
{
for(int j=0;j<=V;j++)
{
f[i][j]=f[i+1][j];
if(j>=v[i])
{
f[i][j]=max(f[i+1][j],f[i+1][j-v[i]]+w[i]);
}
}
}
int vol=V;
for(int i=1;i<=n;i++)
{
if(f[i][vol]==f[i+1][vol-v[i]]+w[i]&&vol>=v[i])
{
cout<<i<<" ";
vol-=v[i];
}
}
return 0;
}
01背包的数据范围问题
1.相比体积而言,价值的范围比较小
我们就将01背包的第二维变成价值。
dp[i][j]=min
(dp[i-1][j],dp[i-1][j-w[i]]+v[i])
最后寻找dp[i][j]<=V最大的j
void solve()
{
memset(dp,0x3f,sizeof dp);
dp[0][0]=0;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=n*W;j++)
{
if(j<v[i])
dp[i][j]=dp[i-1][j];
else
dp[i][j]=min(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
}
}
int res=0;
for(int i=0;i<=n*W;i++)
{
if(dp[n][i]<=W)
res=i;
}
cout<<res;
} 在这里插入代码片
2.体积和价值的范围都较大
折半查找
void solve()
{
int n2=n/2;
for(int i=0;i<1<<n2;i++)//二进制枚举每种选取情况
{
ll sw=0,sv=0;
for(int j=0;j<n2;j++)
{
if(i>>j&1)
{
sw+=w[j];
sv+=v[j];
}
}
pa[i]=make_pair(sw,sv);//之后好排序
}
//如果一个物品体积比另一个物品大,价值又低,就去掉
sort(pa,pa+(1<<n2));
int m=1;
for(int i=1;i<1<<n2;i++)
{
if(pa[m-1].second<pa[i].second)
{
pa[m++]=pa[i];
}
}
//枚举后半部分并求解
ll res=0;
for(int i=0;i<1<<(n-n2);i++)
{
ll sw=0,sv=0;
for(int j=0;j<n-n2;j++)
{
if(i>>j&1)
{
sw+=w[n2+j];
sv+=v[n2+j];
}
}
if(sw<=W)
{
ll tv=(lower_bound(pa,pa+m,make_pair(W-sw,0x3f)))-1)->second;//查找小于W-sw的,v最大的pa
//这个pair用的很巧妙学着点
res=max(res,sv+tv)
}
cout<<res;
}
}
背包问题恰好、至少、至多的处理方法
最长上升子序列
#include<iostream>
#include<cstring>
using namespace std;
const int N=10010;
typedef long long LL;
LL a[N];
LL f[N];
int main()
{
LL n,mx;
cin>>n;
for(LL i=0;i<n;i++)
{
cin>>a[i];
}
//memset(f,1,sizeof f);是不对的只能初始化0或者-1
for(int i=0;i<n;i++)
{
f[i]++;
}
mx=0;
for(LL i=0;i<n;i++)
{
for(LL j=0;j<i;j++)
{
if(a[i]>a[j])
f[i]=max(f[i],f[j]+1);
}
mx=max(mx,f[i]);
}
cout<<mx;
return 0;
}
最长上升子序列的优化
这是个常使用的贪心策略
q[i]表示包含i个数的最长上升子序列的末尾值
这个末尾值一定是单调递增的
这样就可以二分查找最后一个数应当插入的,序列结尾比他小的值的最大值所在的序列。
#include<bits/stdc++.h>
using namespace std;
int a[100001];
int q[100001];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
int len=0;
for(int i=1;i<=n;i++)
{
int l=0,r=len;
while(l<r)
{
int mid=l+r+1>>1;
if(q[mid]<a[i])
l=mid;
else
r=mid-1;
}
len=max(len,r+1);
q[r+1]=a[i];
}
cout<<len;
return 0;
}
最长公共上升子序列
#include<bits/stdc++.h>
using namespace std;
const int N=3001;
int a[N],b[N],f[N][N];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<=n;i++)
{
cin>>b[i];
}
for(int i=1;i<=n;i++)
{
int maxv=1;
for(int j=1;j<=n;j++)
{
f[i][j]=f[i-1][j];
if(a[i]==b[j])
f[i][j]=max(f[i][j],maxv);
if(a[i]>b[j])
maxv=max(maxv,f[i-1][j]+1);
}
}
int res=0;
for(int i=1;i<=n;i++)
{
res=max(res,f[n][i]);
}
cout<<res;
return 0;
}
导弹防御系统
#include<bits/stdc++.h>
using namespace std;
int a[55];
int up[55],down[55];
int ans;
int n,depth;
bool dfs(int depth,int u,int su,int sd)
{
if(su+sd>=depth)//如果是n的话你的剪枝是不到位的
{
return false;
}
if(u==n)
{
return true;
}
int k=0;
while(k<su&&up[k]>=a[u]) k++;
int t=up[k];
up[k]=a[u];
if(k<su)
if(dfs(depth,u+1,su,sd)) return true;
else
if(dfs(depth,u+1,su+1,sd)) return true;
up[k]=t;
k=0;
while(k<sd&&down[k]<=a[u]) k++;
t=down[k];
down[k]=a[u];
if(k<sd)
if(dfs(depth,u+1,su,sd)) return true;
else
if(dfs(depth,u+1,su,sd+1)) return true;
down[k]=t;
}
int main()
{
while(cin>>n,n)
{
ans=n;
for(int i=0;i<n;i++)
{
cin>>a[i];
}
depth=0;
while(!dfs(depth,0,0,0)) depth++;
cout<<depth<<endl;
}
return 0;
}
划分数
n的m划分
dp[m][n]=dp[m-1][n]+dp[m][n-m]
当某个集合包含0,相当于n的m-1划分
当某个集合不包含0,等价于在每个集合抽走1后的划分,即n-m的m划分
组合数学中常用组合数介绍
多重部分和
用dp[i][j]表示枚举到第i个数,他们的和为j是否能实现。
dp[i][j]=dp[i][j]|dp[i][j-ka[i]]
相当于只要能构成和为j,就改变dp[i][j]的状态为1
这个和枚举没啥区别。
一个数组用来放bool值会造成浪费,因此我们可以用dp[i][j]存放用前i种数加和得到j时第i种数剩余多少个。
如果i-1个数能得到j,直接等于m
i
_{i}
i.
否则dp[i][j]=dp[i][j-a
i
_{i}
i]-1
如果用完了也不能得到,就赋值为-1
由于公式里有i也有i-1,我们用完全背包的方式正向枚举体积降维。
void solve()
{
memset(dp,-1,sizeof dp);
dp[0]=0;
for(int i=0;i<n;i++)
{
for(int j=0;j<=k;j++)
{
if(dp[j]>0)
{
dp[j]=m[i];
}
else if(j<a[i]||dp[j-a[i]]<=0)
{
dp[j]=-1;
}
else
{
dp[j]=dp[j-a[i]]-1;
}
}
}
if(dp[k]>=0)
cout<<"yes";
}
Coins
这道题求1到m能被拼成的面值有多少个,和上面的多重部分和是一样的,最后加一个循环统计f[i]为1的情况有多少个。
for(int i=1;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
used[j]=0;
}
for(int j=a[i];j<=m;j++)
{
if(!f[j]&&f[j-a[i]]&&used[j-a[i]]<c[i])
{
f[j]=1;
used[j]=used[j-a[i]]+1;
}
}
}
多重集组合数
dp[i][j]表示从前i种物品取出j个的组合数
dp[i][j]=
∑
k
=
0
m
i
n
(
j
,
a
[
i
]
)
d
p
[
i
]
[
j
−
k
]
\sum_{k=0}^{min(j,a[i])} dp[i][j-k]
∑k=0min(j,a[i])dp[i][j−k]
这个式子可以化简变形
若j ⩽ \leqslant ⩽a i _{i} i
∑ k = 0 m i n ( j , a [ i ] ) d p [ i ] [ j − k ] \sum_{k=0}^{min(j,a[i])} dp[i][j-k] ∑k=0min(j,a[i])dp[i][j−k]
= ∑ k = 0 j d p [ i ] [ j − k ] \sum_{k=0}^{j} dp[i][j-k] ∑k=0jdp[i][j−k]
=dp[i][j]+dp[i][j-1]+…+dp[i][0]
= ∑ k = 0 j − 1 d p [ i ] [ j − 1 − k ] + d p [ i ] [ j ] \sum_{k=0}^{j-1} dp[i][j-1-k]+dp[i][j] ∑k=0j−1dp[i][j−1−k]+dp[i][j]
若j ⩾ \geqslant ⩾a i _{i} i
∑ k = 0 m i n ( j , a [ i ] ) d p [ i ] [ j − k ] \sum_{k=0}^{min(j,a[i])} dp[i][j-k] ∑k=0min(j,a[i])dp[i][j−k]
= ∑ k = 0 a i d p [ i ] [ j − k ] \sum_{k=0}^{a_{i}} dp[i][j-k] ∑k=0aidp[i][j−k]
=dp[i][j]+dp[i][j-1]+…+dp[i][
j
−
a
i
j-a_{i}
j−ai]+dp[i][
j
−
1
−
a
i
j-1-a_{i}
j−1−ai]
-dp[i][
j
−
1
−
a
i
j-1-a_{i}
j−1−ai]
= ∑ k = 0 j − 1 d p [ i ] [ j − 1 − k ] + d p [ i ] [ j ] \sum_{k=0}^{j-1} dp[i][j-1-k]+dp[i][j] ∑k=0j−1dp[i][j−1−k]+dp[i][j]
故
dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i][ j − 1 − a i j-1-a_{i} j−1−ai]
复杂度下降到O(nm)
还要注意初始化,一个都不取得到0个也算一种方案
void solve()
{
for(int i=0;i<=n;i++)
{
dp[i][0]=1;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(j-1-a[i]>=0)//也就是j比a[i]大
{
dp[i][j]=(dp[i-1][j]+dp[i][j-1]-dp[i][j-1-a[i]])%M;
}
else
{
dp[i][j]=(dp[i-1][j]+dp[i][j-1])%M;
}
}
}
}
这里讲了背包问题和计数dp,其他的dp方式将在3.4讲。
背包问题题单
能量石
这道题贪心性质很好想,只要做过国王游戏,不难把两个能量石交换吃的先后顺序的式子列出来。
把吃的时间看成体积,得到的能量看成价值,不难发现是背包解决。但是时间这个体积是没有上限的。可以把吃所有能量石的时间和看做上限,到一定时间以后,有些能量石就会看成只花费吃的时间,不会增加价值的物品,对我们结论的最大价值是没有影响的。
#include<bits/stdc++.h>
using namespace std;
const int N=10010;
int f[N];
struct Stone
{
int s,e,l;
bool operator < (const Stone &w) const
{
return s*w.l<w.s*l;
}
}S[N];
int main()
{
int t;
cin>>t;
int cas=1;
while(t--)
{
int n;
cin>>n;
int m=0;
for(int i=0;i<n;i++)
{
cin>>S[i].s>>S[i].e>>S[i].l;
m+=S[i].s;
}
sort(S,S+n);
memset(f,-0x3f,sizeof f);
f[0]=0;
//之所以用01背包,是因为能量已经到0了我们就可以不用选他了
for(int i=0;i<n;i++)
{
for(int j=m;j>=S[i].s;j--)
{
f[j]=max(f[j],f[j-S[i].s]+S[i].e-(j-S[i].s)*S[i].l);//他的价值就是他拥有的能量减去已经耗费的能量
}
}
int res=0;
for(int i=0;i<=m;i++)
res=max(res,f[i]);
cout<<"Case #"<<cas<<": "<<res<<endl;
cas++;
}
return 0;
}
货币系统
最重要的是想到,只要能被表示出来,就被没有必要有这个数,这样一个性质。
代码中用完全背包凑数,凑不出来的res++即可。
#include<bits/stdc++.h>
using namespace std;
const int N=25010;
int a[N],f[N];
int main()
{
int p;
cin>>p;
while(p--)
{
int n;
cin>>n;
memset(f,0,sizeof f);
memset(a,0,sizeof a);
f[0]=1;
int k=0;
for(int i=1;i<=n;i++)
{
cin>>a[i];
k=max(a[i],k);
}
for(int i=1;i<=n;i++)
{
for(int j=a[i];j<=k;j++)
{
f[j]+=f[j-a[i]];
}
}
int res=0;
for(int i=1;i<=n;i++)
{
if(f[a[i]]==1)
res++;
}
cout<<res<<endl;
}
return 0;
}
金明的预算方案
乍一看是个有依赖的背包问题,可是这是个森林,每棵树只有两层。
观察到每个主件最多有2个附件,即总共一棵树有最多四种情况:无子树,有左,有右,左右都有。
把整个看做一个分组背包问题解决,在分成每一组时加入主件,列举最多4个决策。
思路并不麻烦,但这道题实现起来比较麻烦,要想到如何把附件链接到主件存储,这里用的vector<pair<int,int>>,可以用unordered_map。
另外一个难点是用二进制去枚举各种附件的情况,最后求最大值。
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
const int M=32010;
int f[M];
int w[N];
pair<int,int> a[N];
vector<pair<int,int> > b[N];
int main()
{
int m,n;
cin>>m>>n;
for(int i=1;i<=n;i++)
{
int v,p,q;
cin>>v>>p>>q;
p*=v;
if(q==0)
{
a[i].first=v;
a[i].second=p;
}
else
{
b[q].push_back({v,p});
}
}
for(int i=1;i<=n;i++)
{
for(int k=m;k>=0;k--)//枚举体积
for(int j=0;j<1<<b[i].size();j++)//枚举每一组的各种物品
{
int v=a[i].first;
int w=a[i].second;
for(int g=0;g< b[i].size();g++)//枚举各种物品中细分的选法
{
if(j>>g&1)
{
v+=b[i][g].first;
w+=b[i][g].second;
}
}
if(k>=v)
{
f[k]=max(f[k],f[k-v]+w);
}
}
}
cout<<f[m];
return 0;
}
数字拆分的两道题目
1.数字组合
这是个01背包问题,因为题目给出的那几个数只能用一次。
for(int i=1;i<=n;i++)
{
for(int j=m;j>=a[i];j--)
{
f[j]+=f[j-a[i]];
}
}
2.自然拆分lunatic版
拆成一堆正整数相加,每个正整数一定可以用多次。
所以是完全背包问题呢。
memset(f,0,sizeof f);
f[0]=1;
for(int i=1;i<=n;i++)
{
for(int j=i;j<=n;j--)
{
f[j]=(f[j]+f[j-i])%mod;
}
}
Jury Compromise
计数dp题单