1. 程式人生 > >「LuoguP1430」 序列取數

「LuoguP1430」 序列取數

統計 radius lse 實現 sam getch 個數 inline ace

題目描述

給定一個長為n的整數序列(n<=1000),由A和B輪流取數(A先取)。每個人可從序列的左端或右端取若幹個數(至少一個),但不能兩端都取。所有數都被取走後,兩人分別統計所取數的和作為各自的得分。假設A和B都足夠聰明,都使自己得分盡量高,求A的最終得分。

輸入輸出格式

輸入格式:

第一行,一個正整數T,表示有T組數據。(T<=100)

接著T行,每行第一個數為n,接著n個整數表示給定的序列.

輸出格式:

輸出T行,每行一個整數,表示A的得分

輸入輸出樣例

輸入樣例#1: 復制
2
1 -1
2 1 2
輸出樣例#1:
復制
-1
3

說明

時限: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\n",(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(\n);
71         //輸出答案
72     }
73     return 0;
74 }

「LuoguP1430」 序列取數