dp训练第12题 vijos 1037 搭建双塔 dp

给出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 边界情况:dp[i][j]=-1,dp[0][0]=0

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,这一点我还有一些疑惑,以后继续总结.

你可能感兴趣的:(题解)