hdu2017青岛网络赛Zuma(区间dp)

题面:

Think about the Zuma Game. You have a row of at most 200 black(0) or white(1) balls on the table at the start. Each three consecutive balls never share the same colour. You also have infinite amount of black and white balls in your hand. On each turn, you can choose a ball in your hand and insert it into the row, including the leftmost place and the rightmost place. Then, if there is a group of three of more balls in the same colour touching, remove these balls. Keep doing this until no more balls can be removed.
Find the minimal balls you have to insert to remove all the balls on the table.
思维过程:

只有两种球,我们可以想象相邻颜色相同的球放在一个盒子里,给盒子重现标号,记dp【i】【j】为对第i个盒子到第j个盒子进行操作至少要操作多少次。

先来思考些引理。

引理一:

给一个消去区间(i,j)存在一种消去方式使得最后消去i,j两头依然能保证dp【i】【j】最优。

证明:

运用归纳法,而且是跳步归纳法,dp【i】【i】和dp【i】【i+1】显然成立,当j-i>1时因为如果不放设在中间还能消去时,先消去了i,那么因为相邻的肯定是不相同色的,这意味着消去i和最后用最坏的方法消去i是一样的。另一方面,如果是在消去中间时不得不消去i,那么实际上可以将这一步消去中间的过程留在最后一次操作的上一步。所以我们总可以构造一种方法使得最后消去i,j也能使dp【i】【j】达到最优。

引理二:

我们可以将所有盒子分成很多独立的区间,同时又不影响其他区间,使得总最优等于局部最优之和。

证明:

应用引理一,我们可以知道将分好的区间基本上只留下头尾,相邻的小球颜色不同,不能直接合并,那么我们只能用最朴素的消去方法消去每个区间的头尾,而这并不会影响其他区间。证闭。

推论:

该问题可以使用区间dp解决。

现在我们可以来思考解法了。

解法:

dp【i】【j】时我们怎样能将所有可能的分解情况考虑一遍呢?

我们先考虑一个最显然的一组分解

 dp[j][k]=min(dp[j][k],dp[j][c]+dp[c+1][k])

对于不属于这组分解的其他分解有什么特征吗?

可能从中间开始划分,比如dp【g】【h】,可是为什么不把它划入dp【g】【k】或者dp【j】【h】呢?那什么样的划分不能这样扩展呢?那就是最后留下的g==j,h==k。

也就是说最优应该留下j,h最后进行操作。那么j和h的颜色相等当然最好。因此当color[j]==color[k]时我们可以这样分解if (num[j]+num[k]==2) {dp[j][k]=dp[j+1][k-1]+1; }else dp[j][k]=dp[j+1][k-1];这样够吗?实际上我们好像还可以再优化一种情况能不能在区间(j+1,k-1)间留几个最后合并自动消去?但是如果留两个同色的(三个就自消了)那么只要和其中一端合并就会消失,这样等不到两端合并了,所以num【{j,c,k}】的任意两个的和都小于3。

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <string.h>
using namespace std;
const int maxn=205;
const int Maxn=1<<20;
int color[maxn],num[maxn];
int dp[maxn][maxn];
char ss[maxn];
int main(){
    int t;
    cin>>t;
    int m=0;
    while (t--) {
        m++;
        int n,change=1;
        for (int i=1; i<maxn; i++) {
            num[i]=0;
        }
        scanf("%s",ss);
        int l=strlen(ss);
        int idex=1;
        color[1]=ss[0]-'0';
        num[1]++;
        l--;
        while (l) {
            n=ss[idex]-'0';
            if (n==color[change]) {
                num[change]++;
            }
            else {
                change++;
                color[change]=n;
                num[change]++;
            }
            idex++;
            l--;
        }
        for (int i=1; i<=change; i++) {
            for (int j=1; j<=change; j++) {
                dp[i][j]=Maxn;
            }
        }
        for(int i=1;i<=change;i++){
            dp[i][i]=3-num[i];
        }
        int k;
        for (int i=1; i<change; i++) {
            for (int j=1; j+i<=change; j++) {
                k=j+i;
                if (color[j]==color[k]) {
                    if (num[j]+num[k]==2) {
                        dp[j][k]=dp[j+1][k-1]+1;
                    }
                    else dp[j][k]=dp[j+1][k-1];
                if (num[j]+num[k]<=3) {
                    for (int p=2; p<=k-2-j; p+=2) {
                        if (num[p+j]==1) {
                            dp[j][k]=min(dp[j][k],dp[j+1][p+j-1]+dp[p+j+1][k-1]);
                        }
                    }
                }
                }
                for (int c=j; c<k; c++) {
                    dp[j][k]=min(dp[j][k],dp[j][c]+dp[c+1][k]);
                }
            }
        }
        cout<<"Case"<<" "<<"#"<<m<<":"<<" "<<dp[1][change]<<endl;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值