给出n(100)和n个数(和不超过2000),问能用这些数的一部分或者全部组合成两个相等数,最大为多少?
首先想到的是枚举所有可以表示成的数,即二维状态
状态表示:dp[i][j]表示能否组成i和j这两个数
结果表示:对于0<=i<=1000,取最大的i使得dp[i][i]=1
边界条件:dp[0][0]=1
状态转移:从前转移,dp[i][j]=dp[i-save[k]][j] | dp[i][j-save[k]].save[k]表示第k个数字
有些会超时的感觉,因为1000*1000*100.
但是勉强过了,看来优化很到位
int n=read();
for(int i=1;i<=n;i++)
save[i]=read();
dp[0][0]=1;
for(int k=1;k<=n;k++)
for(int i=1000;i>=0;i--)
for(int j=1000;j>=0;j--)
dp[i+save[k]][j]|=dp[i][j],dp[i][j+save[k]]|=dp[i][j];
int ans=0;
for(int i=1000;i>=0;i--)
if(dp[i][i])
ans=i,i=-1;
if(ans)
printf("%d\n",ans );
else
printf("Impossible\n");
第二种方法来自题解,很巧妙地方法,稍微难写一点点.
状态表示:dp[i][j]:表示第i个数字取完后大数和小数相差为j时最大的大数
结果表示:dp[n][0]
状态转移:向后转移,dp[i][j]可以转移到四个状态,对应第i个数的四种处理方式
1.不放这个数dp[i+1][j]=dp[i][j]
2.放到较大数上dp[i+1][j+save[i+1]]=dp[i][j]+save[i+1]
3.放到较小数上,不超过大数(j>=save[i+1]) dp[i+1][j-save[i+1]]=dp[i][j]
4.放到较小数上,超过大数(j
WA点1:最开始写成了从前转移,i和j太过复杂把我绕晕了
WA点2:结果为0的情况应该输出impossible.
int n=read();
for(int i=1;i<=n;i++)
save[i]=read();
memset(dp,-1,sizeof(dp));
dp[0][0]=0;
for(int i=0;i<=n;i++)
for(int j=0;j<=2000;j++)
{
if(dp[i][j]==-1) continue;
dp[i+1][j]=max(dp[i+1][j],dp[i][j]);
dp[i+1][j+save[i+1]]=max(dp[i+1][j+save[i+1]],dp[i][j]+save[i+1]);
if(j>=save[i+1])
dp[i+1][j-save[i+1]]=max(dp[i+1][j-save[i+1]],dp[i][j]);
else
dp[i+1][save[i+1]-j]=max(dp[i+1][save[i+1]-j],dp[i][j]+save[i+1]-j);
}
if(dp[n][0])
printf("%d\n",dp[n][0] );
else
printf("Impossible\n");
这个题至少可以学习三个点
一个是枚举状态,同时枚举两个,在某些情况下也可行.
一个是简化状态,比如这道题中只需要记录两个数之差就行.
一个是”从前转移”和”向后转移”的想法和写法,一般来说,从前转移难想但是方便,向后转移好表示但是需要置-1,这一点我还有一些疑惑,以后继续总结.