写在前面
这是去年ICPC上海站,我们队伍获得铜尾的比赛,曹佬手撕三维DP怒秀我和郎队,现在来看,好像也没有那么难嘿嘿,可能是自己刷了一些DP题。
题意
给你一个n个物品,每个物品有v,t两个属性,v是值,t是点数,现在你需要从里面取出若干份分成两堆(也就是说有的可以不选),求两堆的点数相等的时候的两堆的值的最大值。嘿嘿,没这么简单,还有一个条件,你最多有k次操作,可以使其中一个点数翻倍,也就是说,有可能让其翻倍之后正好两堆相同,这个时候更大,不然难以找到匹配的让其相等。
思路
其实就是背包问题吧,主要就是一个翻倍的操作,从数据范围来看,n,k并不大,最大才100,点数最多也才13,所以我们定义一个数组dp[i][j][k]
表示前i个物品,已经翻倍了j次,两堆的“分数”达到的k的最大值
这里t最大13,n最大100,那么直接令M=1300,然后分成AB两堆可以理解为:如果放在A堆,那么就加,如果放在B堆,那么就减。比如一开始是k=0,来了一个t=5的,放在A堆,这个时候k=5,这个时候来了一个t=3,如果放在A堆,这个时候是加法,k=8,这个8理解为3+5;那么如果这个放在B堆呢?k=5-3=2,我们最后只需要统计k=0的时候,这个时候是AB两堆相等的时候。
因为有负数,所以我们需要扩大起始点,令此时的M为“0”,也就是本来的(-M,M)变为了(0,2*M),很常见的技巧,然后就进行背包即可。
我们发现每次只与上一次有关联,所以我用的回滚数组,其实开三维也行,我当时是怕爆。
代码
int f[110][2630],g[110][2630];
inline void Case_Test()
{
cin>>n>>k;
for (int i=1;i<=n;i++) cin>>a[i]>>b[i];
memset(f,233,sizeof(f));
f[0][M]=0;
for (int p=1;p<=n;p++)
{
memcpy(g,f,sizeof(f));
for (int i=0;i<p&&i<=k;i++)
{
for (int j=0;j<2*M;j++)
{
if (b[p]<=j) g[i][j]=Max(g[i][j],f[i][j-b[p]]+a[p]);//不翻倍 +
if (b[p]+j<=2*M) g[i][j]=Max(g[i][j],f[i][j+b[p]]+a[p]);//不翻倍 -
if (b[p]*2<=j&&i>0) g[i][j]=Max(g[i][j],f[i-1][j-b[p]*2]+a[p]);//翻倍 +
if (b[p]*2+j<=2*M&&i>0) g[i][j]=Max(g[i][j],f[i-1][j+b[p]*2]+a[p]);//翻倍 -
}
}
memcpy(f,g,sizeof(g));
}
ans=-1e15;
for (int i=0;i<=k;i++)
ans=Max(ans,g[k][M]);//k可以不用完
cout<<ans;
}
文章评论