zoukankan      html  css  js  c++  java
  • 2012百度之星冬季赛第二场第二题 消去游戏I

    题目:

    Alice和Bob又开始发明新游戏了,这回的名字叫消去游戏。

    消去游戏的道具是一堆排成一行的积木,每个积木上面都有一个数字Ai。同时游戏也需要M个额外的数字Di,作为消去的判断条件。当连续的K个积木数字相邻的差值都相同并且和某个Di相等时,即构成一个长度为K等差为Di的等差数列时,他们可以拿去这一段积木,然后不改变剩余积木的顺序,将他们合并。显然,每次至少要消去2个积木。

    现在他们希望知道,在一个给定局面下,能最多消去的积木数目。

    Input

    输入第一行为T,表示有T组测试数据。

    每组数据以两个整数N,M开始,具体意义与描述相同。接着的一行包括N个整数,表示排成一行的积木数字Ai。接下来的一行是M个整数,即给定的消去差值Di。

    1. 1 <= T <= 100

    2. 1 <= N, M <= 300

    3. -1 000 000 000 <= Ai, Di <= 1 000 000 000

    Output

    对每组数据,先输出为第几组数据,然后输出最多能消去的积木数目。

    Sample Input

    3

    3 1

    1 2 3

    1

    3 2

    1 2 4

    1 2

    4 2

    1 3 4 3

    1 2

    Sample Output

    Case 1: 3

    Case 2: 2

    Case 3: 4

    Hint

    第三组数据中,可以先消去第二个和第三个积木,然后消去剩下变为相邻的两个积木。注意不能直接消去第三个和第四个积木,因为它们的差值是-1,而不是1。

    个人题解,结果公布前,暂时不保证能AC:    已AC

    我的解法是DP的,为了便于思考,程序里面用了两次DP,两个其实可以合并的,因为状态其实都是区间的消去情况。

    首先,一次可以消去多个,这个条件必须转化,不然很不利于状态转移,很快就可以发现,假如每次仅允许消去2个是不够的,但是假如每次允许消去2个或者3个,其实就够了,不管多长的区间总可以拆分为每次消去两个和三个的组合。

        

    然后第一个bool dp[N][N],dp[i][j]表示区间[i, j]可不可以消去

    用map储存允许的差值,用mp[d]==true来表示差值允许

    a[i]表示原序列第i个数字

    显然

    一、i>=j时,dp[i][j]=false

      感谢 ACdream的[scu]09p17(copy卡卡西)童鞋指正

      (哎,程序果然还有bug,过了只是运气,我还是太连清了TAT)

      1. i == j时,此时区间为一个数,无法消去,dp[i][j]=false

      2. i > j时,此时区间为空区间,dp[i][j]=true

    二、i+1==j时,mp[a[j] - a[i]]

    三、i+2==j时,a[j] - a[j-1] == a[j-1] - a[i] && mp[a[j] - a[j-1]]

    四、i+2<j

      这时,根据上面得出的,每次删两个或三个这个条件,假如dp[i][j]要等于true,分为两种情况

      1.区间由两个可删除区间组成

        也就是假如存在i<k<j使得 dp[i][k] && dp[k+1][j]

      2.区间是删除了一些子区间后,剩下来的两个或三个数字,正好可以消去

        ①剩下两个可以消去,则这两个必然是头尾,否则在情况1里已经讨论

          即dp[i+1][j-1] && mp[a[j] - a[i]]

        ②剩下三个可以消去,同①理,其中肯定有头尾

          即存在i<k<j使得 a[j] - a[k] == a[k] - a[i] && dp[i+1][k-1] && dp[k+1][j-1] && mp[a[j] - a[k]]

    这样就讨论完了,由于map的速度较慢,而且有很多的状态为false,所以写最后,优化下常数

    状态转移较为复杂,就写成记忆化搜索了,比较简洁。

    得到上面的dp[i][j]后

    我们用ans[i]表示区间[0, i]最多能消去多少个,则

      ans[0]=0;

      ans[i] = Max(

            ans[i-1],                //a[i]没有被消去

            Max(ans[j-1] + dp[j][i]?j-i+1:0)(0<j<i) //a[i]被消去,则肯定有区间[j, i]被消去,消去数为j-i+1,加上之前的区间[0, j-1]的最大消去数

          )

    最终我们得到的ans[n-1]即为区间[0, n-1]的最大消去数,问题得以解决。第一个dp状态数N^2,转移费用由于枚举k最大为N,时间复杂度为N^3, 不过在实际中,由于i>=j的状态基本没用到,加上一旦得出dp[i][j]=true就return的写法,实际应该不容易达到这个上限。

    ans这部分DP状态数为N,转移费用为N

    综合时间复杂度为O(n^3),N=300,还是有些紧张的。结果出来之前,个人觉得觉得有一定可能TLE。百度当天给出两个√。

    如果这题直接用dp[i][j]表示区间[i, j]的最大消去数应该也是可以的,最终答案为dp[0, n-1]

    综合上面两个DP状态转移方程应该也是可以得到比较简单的状态转移方程,不过木有地方测试啊。。。。

    是不是还有可能用四边形优化,求DP大神讨论!!!!!

    以下代码:(百度之星比赛有代码长度的分数,变量为了简介并不与上面题解中一一对应,望谅解)

    bug修改版

     1 #include<cstdio>
     2 #include<cstdlib>
     3 #include<cstring>
     4 #include<iostream>
     5 #include<algorithm>
     6 #include<map>
     7 using namespace std;
     8 const int N=310;
     9 map<int,bool>mp;
    10 int T,C,n,m,a[N],s[N],x;
    11 char q[N][N];
    12 int dfs(int x,int y){
    13     char &ans=q[x][y];
    14     if(ans>=0) return ans;
    15     if(y-x<0) return ans=1;
    16     if(y==x) return ans=0;
    17     if(y-x==1) return ans=mp[a[y]-a[x]];
    18     if(y-x==2) return ans=(a[y]-a[y-1]==a[y-1]-a[x] && mp[a[y-1]-a[x]]);
    19     ans=0;
    20     for(int i=x+1;i<y && !ans;i++){
    21         if(dfs(x,i) && dfs(i+1,y))ans=1;
    22         if(a[i]-a[x] == a[y]-a[i] && dfs(x+1,i-1) && dfs(i+1,y-1) && mp[a[i]-a[x]])
    23             ans=1;
    24     }
    25     if(ans) return ans;
    26     if(dfs(x+1,y-1) && mp[a[y]-a[x]]) ans=1;
    27     return ans;
    28 }
    29 int main()
    30 {
    31     scanf("%d",&T);
    32     while(T--){
    33         printf("Case %d: ",++C);
    34         scanf("%d%d",&n,&m);
    35         mp.clear();
    36         for(int i=0;i<n;i++)scanf("%d",a+i);
    37         for(int i=0;i<m;i++){
    38             scanf("%d",&x);
    39             mp[x]=true;
    40         }
    41         if(n==0){
    42             puts("0");
    43             continue;
    44         }
    45         memset(q,-1,sizeof q);
    46         for(int i=1;i<n;i++){
    47             if(dfs(0,i)){
    48                 s[i]=i+1;
    49                 continue;
    50             }
    51             s[i]=s[i-1];
    52             for(int j=1;j<i;j++)if(dfs(j,i) && s[j-1]+i-j+1>s[i])
    53                 s[i]=s[j-1]+i-j+1;
    54         }
    55         printf("%d\n",s[n-1]);
    56     }
    57     return 0;
    58 }
  • 相关阅读:
    刷题-力扣-112. 路径总和
    刷题-力扣-1137. 第 N 个泰波那契数
    刷题-力扣-101. 对称二叉树
    刷题-力扣-99. 恢复二叉搜索树
    刷题-力扣-109. 有序链表转换二叉搜索树
    刷题-力扣-108. 将有序数组转换为二叉搜索树
    刷题-力扣-111. 二叉树的最小深度
    刷题-力扣-110. 平衡二叉树
    2019春Python程序设计练习6(0423--0429)
    2019春Python程序设计练习5(0416--0422)
  • 原文地址:https://www.cnblogs.com/hundundm/p/2831444.html
Copyright © 2011-2022 走看看