线性,背包,区间DP例题
P1282多米诺骨牌
容易发现一个性质:对于前
状态转移方程:
初始化:
int k(int x){
return x>0?x:-x;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d%d",&a[i],&b[i]);
sum+=a[i]+b[i];
}
for(int i=1;i<=n;++i)
for(int j=0;j<=sum;++j)
f[i][j]=1e9;
f[1][a[1]]=0;f[1][b[1]]=1;
for(int i=2;i<=n;++i)
for(int j=0;j<=sum;++j){
f[i][j]=1e9;
if(j>=a[i])
f[i][j]=min(f[i][j],f[i-1][j-a[i]]);
if(j>=b[i])
f[i][j]=min(f[i][j],f[i-1][j-b[i]]+1);
}
minn=1e9;
for(int i=0;i<=sum;++i)
if(f[n][i]!=1e9&&k(sum-i-i)<minn)
minn=k(sum-i*2),ans=f[n][i];
else if(f[n][i]!=1e9&&k(sum-i-i)==minn)
ans=min(ans,f[n][i]);
printf("%d\n",ans);
return 0;
}
P4138挂饰
设
至少
状态转移方程:
初始化:
需要注意的是,在
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d%d",&p[i].a,&p[i].b);
sort(p+1,p+n+1,cmp);
for(int i=0;i<=n;++i)
for(int j=0;j<=n+1;++j)
f[i][j]=-1e9;
f[0][1]=0;
for(int i=1;i<=n;++i){
for(int j=n;j>=0;--j){
f[i][j]=max(f[i-1][j],f[i-1][max(j-p[i].a,0)+1]+p[i].b);
}
}
for(int i=0;i<=n;++i)
ans=max(ans,f[n][i]);
printf("%d\n",ans);
return 0;
}
P2679子串
设
状态转移方程:
......
如果直接算,时间复杂度是
另外,此时的空间复杂度为
int main(){
cin>>n>>m>>k;
cin>>a>>b;
sum[0][0]=1;
for(ll i=1;i<=n;++i){
for(ll j=min(i,m);j>=1;--j){
for(ll p=min(j,k);p>=1;--p){
if(a[i-1]==b[j-1])
f[j][p]=(f[j-1][p]+sum[j-1][p-1])%mod;
else
f[j][p]=0;
sum[j][p]=(sum[j][p]+f[j][p])%mod;//sum[i][j][l]维护f[1][j][l]~f[i][j][l]的前缀和
}
}
}
cout<<sum[m][k]%mod<<endl;
return 0;
}
P5662 [CSP-J2019] 纪念品
题目中有一句关键的话“每天卖出纪念品换回的金币可以立即用于购买纪念品,当日购买的纪念品也可以当日卖出换回金币。”我们可以根据这句话简化问题:把第
设
考虑滚动数组,我们发现只需要保留
int main(){
cin>>t>>n>>m;
for(int i=1;i<=t;++i)
for(int j=1;j<=n;++j)
cin>>a[i][j];
ans=m;
for(int i=1;i<=t-1;++i){
for(int l=0;l<=ans;++l)
dp[l]=0;
for(int j=1;j<=n;++j){
for(int k=a[i][j];k<=ans;++k){
dp[k]=max(dp[k],dp[k-a[i][j]]-a[i][j]+a[i+1][j]);
}
}
ans+=dp[ans];
}
cout<<ans<<endl;
fclose(stdin);fclose(stdout);
return 0;
}
P4059找爸爸
首先要知道,两个序列的某一个位置都是空格的情况肯定不是最优解。因为题目中k的系数是负数,k增加会令答案更劣。所以对于一个位置只考虑三种情况:
1:
2:
3:
设
初始化:
状态转移方程:
int main(){
cin>>x>>y;
cin>>d['A']['A']>>d['A']['T']>>d['A']['G']>>d['A']['C'];
cin>>d['T']['A']>>d['T']['T']>>d['T']['G']>>d['T']['C'];
cin>>d['G']['A']>>d['G']['T']>>d['G']['G']>>d['G']['C'];
cin>>d['C']['A']>>d['C']['T']>>d['C']['G']>>d['C']['C'];
cin>>a>>b;
for(int i=0;i<y.size();++i){
f[0][i+1][1]=-a-b*i;
f[0][i+1][2]=f[0][i+1][0]=-1e9;
}
for(int i=0;i<x.size();++i){
f[i+1][0][2]=-a-b*i;
f[i+1][0][1]=f[i+1][0][0]=-1e9;
}
f[0][0][1]=f[0][0][2]=-1e9;
for(int i=0;i<x.size();++i)
for(int j=0;j<y.size();++j){
f[i+1][j+1][0]=max(f[i][j][0],max(f[i][j][1],f[i][j][2]))+d[x[i]][y[j]];
f[i+1][j+1][1]=max(f[i+1][j][0]-a,max(f[i+1][j][1]-b,f[i+1][j][2]-a));
f[i+1][j+1][2]=max(f[i][j+1][0]-a,max(f[i][j+1][1]-a,f[i][j+1][2]-b));
}
printf("%d\n",max(f[x.size()][y.size()][0],max(f[x.size()][y.size()][1],f[x.size()][y.size()][2])));
return 0;
}
P7961 [NOIP2021]数列
考虑按照
设
如果从前面转移到
假设当前序列
状态转移方程:
long long bit1(long long x){
long long cnt=0;
while(x){
x-=x&(-x);
++cnt;
}
return cnt;
}
int main(){
scanf("%lld%lld%lld",&n,&m,&p);
for(int i=0;i<=m;++i){
scanf("%lld",&v[i]);
pv[i][0]=1;
for(int j=1;j<=n;++j)
pv[i][j]=pv[i][j-1]*v[i]%mod;
}
for(int i=0;i<=100;++i)
c[i][0]=1;
for(int i=1;i<=100;++i)
for(int j=1;j<=100;++j)
c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
f[0][0][0][0]=1;
for(int i=0;i<=m;++i)
for(int j=0;j<=n;++j)
for(int k=0;k<=p;++k)
for(int l=0;l<=(n>>1);++l)
for(int t=0;t<=n-j;++t)
f[i+1][j+t][k+((t+l)&1)][(l+t)>>1]=(f[i+1][j+t][k+((t+l)&1)][(l+t)>>1]+f[i][j][k][l]*c[n-j][t]%mod*pv[i][t]%mod)%mod;
for(int i=0;i<=p;++i)
for(int j=0;j<=(n>>1);++j)
if(i+bit1(j)<=p)
ans=(ans+f[m+1][n][i][j])%mod;
printf("%lld\n",ans);
return 0;
}
P2224产品加工
看到这道题,不难想到暴力DP:设
那么怎么优化呢?这道题采用了一种很新颖的方法:将答案设为状态。设
状态转移方程:
不难发现,在倒叙枚举
另外,直接写的时间复杂度为
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d%d%d",&a[i],&b[i],&c[i]);
ans=1e9;
for(int i=1;i<=30000;++i)
f[i]=1e9;
for(int i=1;i<=n;++i){
up+=max(a[i],c[i]);
for(int j=up;j>=0;--j){
int x,y,z;x=y=z=1e9;
if(a[i]&&j>=a[i])
x=f[j-a[i]];
if(b[i])
y=f[j]+b[i];
if(c[i]&&j>=c[i])
z=f[j-c[i]]+c[i];
f[j]=min(x,min(y,z));
if(i==n)
ans=min(ans,max(j,f[j]));
}
}
printf("%d\n",ans);
return 0;
}
P1858多人背包
设
接下来考虑转移。首先外层枚举每个物品
for(int i=1;i<=n;++i){
for(int j=v;j>=a[i];--j){
int k1=1,k2=1,cnt=0;
for(int l=1;l<=k;++l)
now[j][l]=f[j][l];
while(cnt<=k){
if(f[j][k1]>f[j-a[i]][k2]+b[i]){
now[j][++cnt]=f[j][k1];
++k1;
}
else{
now[j][++cnt]=f[j-a[i]][k2]+b[i];
++k2;
}
}
for(int l=1;l<=k;++l)
f[j][l]=now[j][l];
}
}
方块消除 Blocks
经典的区间消除问题。
传统的想法是,设
因为内部的块会受到外部的块的影响,所以设
首先考虑第一种情况:
第二种情况:区间
int dfs(int l,int r,int k){
if(l>r)
return 0;
if(dp[l][r][k])
return dp[l][r][k];
dp[l][r][k]=max(dp[l][r][k],dfs(l,r-1,0)+(k+1)*(k+1));
for(int p=nxt[r];p>=l;p=nxt[p])
dp[l][r][k]=max(dp[l][r][k],dfs(l,p,k+1)+dfs(p+1,r-1,0));
return dp[l][r][k];
}
signed main(){
scanf("%lld",&t);
while(t--){
scanf("%lld",&n);
for(int i=1;i<=n;++i)
scanf("%lld",&a[i]),nxt[i]=0;
for(int i=1;i<=n;++i)
for(int j=i-1;j>=1;--j)
if(a[j]==a[i])
{nxt[i]=j;break;}
for(int i=1;i<=n;++i)
for(int j=i;j<=n;++j)
for(int k=0;k<=n;++k)
dp[i][j][k]=0;
printf("Case %lld: %lld\n",++cnt,dfs(1,n,0));
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探