令第一个字符串为a,第二个字符串为b,f[i][j]表示到a第i个字母为止和到b第j个字母为止的lcs。
当a[i]==b[j], 则f[i][j]=f[i-1][j-1]+1,即之前的子串的最大公共子序列加上目前相等的这个字母;
当a[i]!=b[j], 则f[i][j]=max(f[i][j-1],f[i-1][j]),即拿a在i之前的子串与目前的b比较、拿b在j之前的子串与目前的a比较。
边界点:若a[1]==b[1],则f[1][1]=1;否则f[1][1]=0。
细节问题:若是记忆化搜索,因为当为0时也需要作为值返回,所以清空数组的时候要用-1.不然会超时QAQ
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f, maxn=205;
string a,b;
int f[maxn][maxn],la,lb;
//int calc(int i,int j){
// if(f[i][j]!=-1) return f[i][j];
// if(i<1||j<1) return f[i][j]=0;
// if(i==1&&j==1){
// if(a[i]!=b[j]) return 0;
// else return 1;
// }
// if(a[i]==b[j]) f[i][j]=calc(i-1,j-1)+1;
// else f[i][j]=max(calc(i,j-1),calc(i-1,j));
// return f[i][j];
//}
int main(){
while(cin>>a>>b){
// memset(f,-1,sizeof(f));
memset(f,0,sizeof(f));
la=a.length(); lb=b.length();
a='0'+a; b='0'+b;
cout<<a<<endl;
for(int i=1;i<=la;++i){
for(int j=1;j<=lb;++j){
if(i==1&&j==1){
if(a[i]!=b[j]) f[1][1]=0;
else f[1][1]=1;
continue;
}
if(a[i]==b[j]) f[i][j]=f[i-1][j-1]+1;
else f[i][j]=max(f[i][j-1],f[i-1][j]);
}
}
cout<<f[la][lb]<<endl;
// cout<<calc(la,lb)<<endl;
}
return 0;
}
由于我们只在意最后还剩多少容量,所以可以考虑用可行性描述:bool数组f[i][j]表示前i个物体能否放满体积为j的背包,通过枚举最后一次决定——第i个物品放不放,f[i][j] = f[i-1][j] || f[i-1][j-v[i]],同时初值设f[0][0]=1。
细节问题:都说是前i个了,所以一定要记得让数组继承前一个容纳情况的信息!这就要求j必须从0开始循环,保证信息有效继承!爆哭QAQ
01滚动数组——一个节省空间的做法,就是不断更新数组为当前2个,由于更新数组仅需要前一行的信息,所以只要保留两行数组就好了,方法就是直接对行模2。不过注意,这里滚动的时候数组没有清0是因为本来就继承上面的信息!在其他题目中需要多加小心。
就地滚动——更常用的方法,但需要注意的是,如果从小到大循环数组,会使得某个物品加入不止一次(比如,体积为5的箱子在循环到总体积为10时,因为不能够识别之前的5是在本轮循环中刚加入过的,导致10处也被标记,相当于又加入了一次,15/20等等也重复这种错误),这时我们可以通过倒序循环,就可以避免这种情况啦。
01滚动数组:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f, maxn=35;
int x,n,ans,v[35],f[2][20005];
int main(){
cin>>x>>n;
for(int i=1;i<=n;++i)
cin>>v[i];
for(int i=0;i<=n;++i)
f[i][0]=1;
for(int i=1;i<=n;++i){
for(int j=0;j<=x;++j){
if( j >= v[i] )f[i%2][j]=f[(i-1)%2][j]||f[(i-1)%2][j-v[i]];
else f[i%2][j] = f[(i-1)%2][j];
if(f[i%2][j]){
ans=max(j,ans);
}
}
}
cout<<x-ans;
return 0;
}
就地滚动数组:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f, maxn=35;
int x,n,ans,v[35],f[20005];
int main(){
cin>>x>>n;
for(int i=1;i<=n;++i)
cin>>v[i];
f[0]=1;
for(int i=1;i<=n;++i){
for(int j=x;j>=v[i];--j){
f[j]=f[j]||f[j-v[i]];
if(f[j]) ans=max(ans,j);
}
}
cout<<x-ans;
return 0;
}
背包
设f[i][j]表示前i种草药中选,在时间j之前能够采到的最大价值,c[i]为花费时间,w[i]为草药价值。则可以得到状态转移方程:
f[i][j]=max(f[i-1][j], f[i-1][j-c[i]]+w[i]);
即前i种草药中选、时间j之前能够采到的最大价值 等于 不选第i种草药 与选第i种草药的最大价值。注意,当初始值设为-inf、f[i][0]=0的时候不能保证最终答案出现在最后一个数组空间中,而是确定了怎样不会有剩余时间。
但也可以为没有装满的部分填充“时间”,所有初值设为0可以达到此效果,即假设它用了目前所有的时间,而把最大的数存起来,这样就可以在数组最后轻松取出最大值。不过需要注意,题目中若有明确要求不能有剩余时间,就不可以这样做了。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f, maxn=105;
int t,m,ans,c[maxn],w[maxn],f[1005];
int main(){
cin>>t>>m;
for(int i=1;i<=m;++i)
cin>>c[i]>>w[i];
for(int i=1;i<=m;++i){
for(int j=t;j>=1;--j)
if(j>=c[i]) f[j]=max(f[j],f[j-c[i]]+w[i]);
}
for(int i=1;i<=t;++i)
ans=max(ans,f[i]);
cout<<ans;
return 0;
}
扩展
M<=20, T<=10^9:暴搜
M<=40, T<=10^9:分半搜索
M<=100, T<=10^9:去掉无效的空间(第一不会存在没有用的时间,第二不会存在比时间少的价值还低的无用的点),使用map进行信息存储。
转移方程:f[i][j]=max(f[i-1][j], f[i][j- k* c[i]]+ k* w[i]),我们发现就是要不断放入同一个草药,于是想起来之前在01背包中就地滚动时的顺序滚动会重复加入的特点,其实就是它。
细节问题:注意数据范围!
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f, maxn=10000005;
ll t,m,ans,c[maxn],w[maxn],f[maxn];
int main(){
cin>>t>>m;
for(int i=1;i<=m;++i)
cin>>c[i]>>w[i];
for(int i=1;i<=m;++i)
for(int j=c[i];j<=t;++j)
f[j]=max(f[j],f[j-c[i]]+w[i]);
cout<<f[t];
return 0;
}
首先,此题可以被拆分为01背包做。优化方法就是可以通过将过多的物品打包成2的0~k次幂。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f, maxn=10000005;
ll t,n,V,num,v[maxn],w[maxn],f[105];
int main(){
cin>>n>>V;
int vv,ww,ss;
while(n--){
cin>>vv>>ww>>ss;
int i=1;
while(ss>0){
num++;
v[num]=i*vv;
w[num]=i*ww;
ss-=i;
if(ss>i) i*=2;
else i=ss;
}
}
for(int i=1;i<=num;++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;
}
当发现题目是由熟悉的动态规划题目添加了限制条件变形得到,可以在原题状态加一维做。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f, maxn=1005;
ll N,V,M,v[maxn],m[maxn],w[maxn],f[maxn][maxn];
int main(){
cin>>N>>V>>M;
for(int i=1;i<=N;++i)
cin>>v[i]>>m[i]>>w[i];
for(int i=1;i<=N;++i)
for(int j=V;j>=v[i];--j)
for(int k=M;k>=m[i];--k)
f[j][k]=max(f[j][k],f[j-v[i]][k-m[i]]+w[i]);
cout<<f[V][M];
return 0;
}
分组背包就是在每个组中挑一个,求总价值最大。需要注意的是循环顺序:先枚举组数,再枚举体积,再枚举组内每个物品。如果将枚举组内每个物品和枚举体积反过来,就成为了01背包。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f, maxn=105;
struct ty{
int v[maxn],w[maxn],s;
}g[maxn];
int N,V,s,f[maxn];
int main(){
cin>>N>>V;
for(int i=1;i<=N;++i){
cin>>s;
g[i].s=s;
for(int j=1;j<=s;++j)
cin>>g[i].v[j]>>g[i].w[j];
}
for(int i=1;i<=N;++i)
for(int j=V;j>=1;--j)
for(int k=1;k<=g[i].s;++k)
if(j>=g[i].v[k])
f[j]=max(f[j],f[j-g[i].v[k]]+g[i].w[k]);
cout<<f[V];
return 0;
}