题目:
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 }