题目描述
给定一个长为n的整数序列(n<=1000),由A和B轮流取数(A先取)。每个人可从序列的左端或右端取若干个数(至少一个),但不能两端都取。所有数都被取走后,两人分别统计所取数的和作为各自的得分。假设A和B都足够聪明,都使自己得分尽量高,求A的最终得分。
输入输出格式
输入格式:第一行,一个正整数T,表示有T组数据。(T<=100)
接着T行,每行第一个数为n,接着n个整数表示给定的序列.
输出格式:输出T行,每行一个整数,表示A的得分
输入输出样例
说明
时限:3s
题解
首先让我们试试暴力思路:
设A的得分为VA,B的得分为VB
那么在(VA-VB)取得最大值时,有VA最大。
证明:VA-VB=VA-(Sum-VA)=2*VA-Sum
设F[L][R]为当前还剩[L][R]时,(先手得分-后手得分)的最大值。
若当前正在处理F[L][R],那么存在三种选择方案:
1.全取。
F[L][R]=∑{ v[i] | L < = i < = R }
2.从左边取一些。(保留L'到R)
F[L][R]=∑{ v[i] | L <= i <= L'-1 }-F[L'][R]
3.从右边取一些。(保留L到R')
F[L][R]=∑{ v[i] | R'+1 <= i <= R }-F[L][R']
关于减去F[L'][R]:
当区间转换到[L',R]时的F值,为转换后的先手-后手,也就是当前意义下的后手-先手,
所以-(后手-先手)=先手-后手。
这样,我们从小到大枚举区间,每次在这三种决策中取一种最优决策,
最后的max(VA-VB)=F[1][n],
max(VA)=(F[1][n]+Sum)/2。
于是我们可以很较为轻松的写出O(n^3)的暴力啦!
1 /* 2 qwerta 3 P1430 序列取数 4 Unaccepted 5 40 6 代码 C++,0.94KB 7 提交时间 2018-09-19 18:57:13 8 耗时/内存 9 19351ms, 4628KB 10 */ 11 #include<cmath> 12 #include<cstdio> 13 #include<cstring> 14 #include<iostream> 15 using namespace std; 16 #define R register 17 inline int read() 18 { 19 char ch=getchar(); 20 int x=0;bool s=1; 21 while(!isdigit(ch)){if(ch=='-')s=0;ch=getchar();} 22 while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} 23 return s?x:-x; 24 } 25 int s[1007]; 26 int f[1007][1007]; 27 //int m[1007][1007]; 28 int main() 29 { 30 //freopen("a.in","r",stdin); 31 int t=read(); 32 while(t--) 33 { 34 int n=read(); 35 for(R int i=1;i<=n;++i) 36 s[i]=s[i-1]+read(); 37 for(R int len=1;len<=n;++len) 38 for(R int l=1;l+len-1<=n;++l) 39 { 40 41 int r=l+len-1; 42 f[l][r]=s[r]-s[l-1]; 43 for(R int _l=l+1;_l<=r;++_l) 44 f[l][r]=max(f[l][r],s[_l-1]-s[l-1]-f[_l][r]); 45 for(R int _r=r-1;_r>=l;--_r) 46 f[l][r]=max(f[l][r],s[r]-s[_r]-f[l][_r]); 47 48 } 49 printf("%d ",(f[1][n]+s[n])>>1); 50 } 51 return 0; 52 }
但是想要通过1e3的数据,O(n^3*t)的时间复杂度肯定是布星的。
所以我们还需要一点儿优化。
考虑状态数是n^2的,雷打不动,所以我们要将贼手伸向状态转移。
以从右取为例:
F[L][R]=max(S[R]-S[R']-F[L][R']) //(L <= R' <= R-1)
=max(S[R]-(S[R']+F[L][R']))
如果我们能知道R'在L到R-1上时,S[R']+F[L][R']的最小值,那么就能O(1)转移了。
设
Min[L][R]=min{S[R']+F[L][R'] | L <= R' <= R }
那么有
Min[L][R-1]=min{S[R']+F[L][R'] | L <= R' <= R-1 }
也就是说
Min[L][R]=min(Min[L][R-1],S[R]+F[L][R])
这样,在循环枚举区间的同时顺便维护一下Min[L][R],就可以实现O(1)转移了。
其实就是把暴力中间那两句话换了种说法而已。
1 /* 2 qwerta 3 P1430 序列取数 4 Accepted 5 100 6 代码 C++,1.63KB 7 提交时间 2018-09-19 20:05:27 8 耗时/内存 9 4910ms, 12444KB 10 */ 11 #include<cmath> 12 #include<cstdio> 13 #include<cstring> 14 #include<iostream> 15 using namespace std; 16 #define R register 17 inline int read() 18 { 19 char ch=getchar(); 20 int x=0;bool s=1; 21 while(!isdigit(ch)){if(ch=='-')s=0;ch=getchar();} 22 while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} 23 return s?x:-x; 24 }//快读(这题略卡常) 25 int s[1007]; 26 int f[1007][1007]; 27 int ml[1007][1007]; 28 int mr[1007][1007]; 29 //设ml为固定L端时的min值,mr为固定R端时的max值 30 void write(int x) 31 { 32 if(x>9)write(x/10); 33 putchar(x%10+'0'); 34 return; 35 }//快写 36 int main() 37 { 38 //freopen("a.in","r",stdin); 39 int t=read(); 40 while(t--) 41 { 42 int n=read(); 43 for(R int i=1;i<=n;++i) 44 s[i]=s[i-1]+read();//前缀和 45 for(R int l=1;l<=n;++l) 46 { 47 f[l][l]=s[l]-s[l-1]; 48 ml[l][l]=s[l]+f[l][l]; 49 mr[l][l]=s[l-1]-f[l][l]; 50 }//预处理 51 for(R int len=2;len<=n;++len) 52 for(R int l=1,r=len;r<=n;++l,++r)//枚举区间 53 { 54 f[l][r]=s[r]-s[l-1]; 55 //mr 56 //for(R int _l=l+1;_l<=r;++_l) 57 //f[l][r]=max(f[l][r],s[_l-1]-s[l-1]-f[_l][r]); 58 f[l][r]=max(f[l][r],mr[l+1][r]-s[l-1]); 59 //ml 60 //for(R int _r=r-1;_r>=l;--_r) 61 //f[l][r]=max(f[l][r],s[r]-s[_r]-f[l][_r]); 62 f[l][r]=max(f[l][r],s[r]-ml[l][r-1]); 63 // 64 ml[l][r]=min(ml[l][r-1],s[r]+f[l][r]); 65 mr[l][r]=max(mr[l+1][r],s[l-1]-f[l][r]); 66 } 67 int x=(f[1][n]+s[n])>>1; 68 if(x<0){putchar('-');write(-x);} 69 else write(x); 70 putchar(' '); 71 //输出答案 72 } 73 return 0; 74 }