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 }
原文地址:https://www.cnblogs.com/hundundm/p/2831444.html