1. 程式人生 > >[JZOJ]雜題選講

[JZOJ]雜題選講

目錄

      1.aplusb

2.可見點數

3.射擊

4.創世紀

5.長方形

6.連通塊

7.Ede的新揹包問題

8.模板串

9.Clock Sequence

10.硬幣遊戲


1.aplusb

Description:

SillyHook要給小朋友出題了,他想,對於初學者,第一題肯定是a+b 啊,但當他出完資料後神奇地發現.in不見了,只留下了一些.out,他想還原.in,但情況實在太多了,於是他想要使得[a,b] ([a,b] 表示a,b 的最小公倍數)儘可能大。

Input:

輸入檔案的第一行一個整數T 表示資料組數。
接下來T行每行一個整數n ,表示.out中的數值,即a+b=n 。

Output:

共T行,每行一個整數表示最大的[a,b] 的值。

Data Constraint:

 30%的資料滿足 T<=10,n<=1000
100% 的資料滿足T<=10000 ,n<=10^9

Solutions:

實際是一道結論題,但我不會證明,我用比較暴力的方法也過了。。。

顯然a和b的值越接近越好,於是把令p = n / 2 + 1,然後往後列舉,找到的第一個

gcd(i, n - i) = 1 的就是答案。

Code:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cmath>
#define LL long long
using namespace std;
LL n, Q;
inline LL read()
{
    LL s=0; char ch=getchar();
    for(;ch<'0'||ch>'9';ch=getchar());
    for(;ch>='0'&&ch<='9';s=s*10+ch-'0',ch=getchar());
    return s;
}
inline LL Gcd(LL x, LL y)
{
    for (; y != 0; )
{
        swap(x, y);
        y = y % x;
    }
    return x;
}
inline void Gets()
{
    LL p=n>>1|1;
    for(register int i=p;i<=n;i++)
{
        LL g = Gcd(i, n - i);
        if (g==1){
            printf("%lld\n",i*(n-i));
            return;
        }
    }
}

int main()
{
    Q=read();
    for(;Q--;)
{
        n=read();
        Gets();
    }
}

2.可見點數

Description:

ZPS經過長期的努力爭取,終於成為了0901班的領操員,他要帶領0901班參加廣播操比賽。現在0901班的隊伍可以看作是一個n*n的點陣,每個人都站在格點上。現在作為領操員的ZPS站(0,0)點,他想知道如果0901班的隊伍站齊了,他能看到多少個人的臉(假設每個人的身高相同,體積相同)。

Input:

一個正整數n。

Output:

ZPS能看到多少個人的臉(當然他是看不到自己的臉的)。

Data Constraint:

40%的資料,n<=1500。
100%的資料,n<=100000。

Solutions:

這題很顯然兩個座標互質就可以看得到,用尤拉函式求就行了。

Code:

#include <cstdio>
#define maxn 100000
using namespace std;

int f[maxn];

int main()
{
    int n;
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
        f[i]=i;
    for (int i=2;i<=n;i++)
        if (i%2==0) f[i]/=2;
            else if (f[i]==i)
            {
                for (int j=i;j<=n;j+=i)
                    f[j]=f[j]/i*(i-1);
            }
    long long ans=0;
    for (int i=1;i<n;i++)
        ans+=f[i];
    if (n==1) printf("0");
        else printf("%lld",ans*2+1);
}

3.射擊

Description:

有問題,找副連,無聊的時候當然也可以找他啦。小W找到了他的叔叔——東廠廠長——宇宙超級無敵老WS yy。他們叔侄兩個商量之後決定用彈弓打破社群裡的一些窗戶,但是彈弓每秒只能徹底打破一扇窗戶。而且如果某戶窗戶的主人回來了的話,他們就不能進行破壞了(不然會死得很慘的)。因為有的人裝的玻璃好,有的人裝的玻璃差,有的人裝的玻璃高,有的人裝的玻璃矮,所以你不能要求他們叔侄兩個打破不同的窗戶獲得的快樂值必須相同。現在他們想知道在能活著的情況下能夠獲得的最大快樂值。

Input:

第一行一個正整數n,表示共有n個窗戶。
接下來n行,每行兩個整數,第一個為窗子的主人回來的時刻(秒),第二個為破壞該窗戶所能獲得的快樂值。

Output:

最大的快樂值。

Data Constraint:

20%的資料,n<=100。
40%的資料,n<=50000。
100%的資料,n<=200000,快樂值的絕對值不超過32767,時刻非負且小於2^31。

Solutions:

將時間從大到小排序,那麼顯然在一個主人先回來的窗戶前面的窗可以在這之前的任何時間裡打破,每次判斷當前點的價值是否為負數,如果是非負數的話,那麼我們就講這個點壓入堆中,然後在它距離下一個時間點的時間裡,我們可以在堆中取啊a[i].t-1-a[i+1].t個點,就可以了

Code:

#include <stdio.h>
#include <queue>
#include <algorithm>
using namespace std;
#define maxn 200005
struct arr
{
    long long t, w;
}a[maxn];
int cmp(arr a, arr b)
{
    return a.t > b.t;
}
priority_queue<long long,vector<long long>,less<long long> > q;
int main()
{
    long long n;
    scanf("%lld", &n);
    for (int i = 1; i <= n; i++)
        scanf("%lld%lld", &a[i].t, &a[i].w);
    sort(a + 1, a + n + 1, cmp);
    long long ans = 0;

    for (int i = 1; i <= n; i++)
    {
        if (a[i].w >= 0)
            q.push(a[i].w);
        else continue;
        for (int j = a[i + 1].t; j <= a[i].t - 1; j++)
        {
            ans += q.top();
            q.pop();
            if (q.empty()) break;
        }
    }
    printf("%lld\n", ans);
}

4.創世紀

Description:

上帝手中有著n種被稱作“世界元素”的東西,現在他要把它們中的一部分投放到一個新的空間中去以建造世界。每種世界元素都可以限制另外一種世界元素,所以說上帝希望所有被投放的世界元素都有至少一個沒有被投放的世界元素能夠限制它,這樣上帝就可以保持對世界的控制。
由於那個著名的有關於上帝能不能製造一塊連自己都不能舉起的大石頭的二律背反命題,我們知道上帝不是萬能的,而且不但不是萬能的,他甚至有事情需要找你幫忙——上帝希望知道他最多可以投放多少種世界元素,但是他只會O(2^n)級別的演算法。雖然上帝擁有無限多的時間,但是他也是個急性子。你需要幫助上帝解決這個問題。

Input:

第一行一個正整數n,表示世界元素的數目。
第二行n個正整數a_1, a_2, ..., a_n。a_i表示第i個世界元素能夠限制的世界元素的編號。

Output:

最多可以投放的世界元素的數目。

Data Constraint:

30%的資料,n<=10。
60%的資料,n<=10^5。
100%的資料,a_i<=n<=10^6。

Solutions:

好像有樹形dp的解法,但我只寫了貪心。

一句話概括的話:從入度為0的點開始按拓撲深度分層,每一層的元素個數總是不增的,於是當前層能選則選。(我不知道用詞對不對qwq)

大概就是入度為0的點,我必須留下,而他連線的點,如果沒有被要求必須留下,那麼他就丟掉,最終會剩下若干個環,環的情況我們

只需要留下一半的元素即可(模擬很容易發現)。

至於證明,可以用上面說的“不增”來理解。

也可以“反證”,假設1入度為0,那麼1留下,1指向2,那麼2丟棄,如果不丟2,那麼我們可以選擇丟2指向的3,但這樣肯定不會更優,因為還可能有別的指向3,因為每個點(比如2)只會引出一條邊,如果這個點不選,那他帶來的收益也就是他指向的點可以選上了,而他指向的點還有可能被別的點所指向,也就是說盡管我丟掉了當前點,我指向的點依然可能被丟掉,那麼這樣貢獻是2(或者1),而不丟掉當前點的貢獻是1,保證不會更優,所以貪心正確。

Code:

#include<bits/stdc++.h>
using namespace std;
int n,a[1000006],in[1000006],vis[1000006];
queue<int>S;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    scanf("%d",&a[i]),in[a[i]]++;
    for(int i=1;i<=n;i++)
    {    
        if(!in[i])
    {
            S.push(i);
            vis[i]=1;
        }
    }
    int ans=0;
    while(!S.empty())
    {
        int u=S.front();
        S.pop();
        if(!vis[a[u]])
    {
            vis[a[u]]=2;
            ans++;
            in[a[a[u]]]--;
            if(!in[a[a[u]]])
            {
                S.push(a[a[u]]);
                vis[a[a[u]]]=1;
            }
        }
    }
    for(int i=1;i<=n;i++)
{
        if(vis[i])continue;
        int cnt=0,now=i;
        while(!vis[now])
{
            vis[now]=1;
            cnt++;
            now=a[now];
        }
        ans+=cnt/2;
    }
    printf("%d\n",ans);
    return 0;
}

5.長方形

Description:

雞腿是CZYZ的著名DS,但是不想學數學的DS不是好GFS,所以雞腿想通過提高數學水平來增強他的GFS氣質!雖然你對雞腿很無語,但是故事的設定是你幫助雞腿增強了GFS氣質,所以現在你必須教雞腿學數學!
雞腿想到了一個很高(sha)明(bi)的問題,在 N 條水平線與 M 條豎直線構成的網格中,放 K 枚石子,每個石子都只能放在網格的交叉點上。問在最優的擺放方式下,最多能找到多少四邊平行於座標軸的長方形,它的四個角上都恰好放著一枚石子。 

Input:

一行輸入三個正整數N,M,K。 

Output:

一行輸出一個正整數,表示最多的滿足條件的長方形數量。

Data Constraint:

對於50%的資料0 < N, M ≤ 30;
對於100%的資料0 < N, M ≤ 30000;K ≤ N*M。

Solutions:

顯然k個點必定擺成以下兩種情況可能最優:

            XXX...XXX

            XXX...XXX

             .

             .

             .

             XXX...XXX

             X..X

             XXX...XXXX

             XXX...XXX.

             .        . 

             .        X

             .       

             XXX...XXX

所以只要列舉一行的個數(或者一列的個數)。

假設第一種情況除了最後一行每行都是i個,那麼矩形的個數就是C(k/i,2)*C(i,2)+C(k%i,2)*k/i,第二種情況同理。

Code:

#include<cstdio>
using namespace std;
long long a,b,c,i,sum,ans,x,y;
int main()
{
	freopen("rectangle.in","r",stdin);
	freopen("rectangle.out","w",stdout);
	scanf("%lld%lld%lld",&a,&b,&c);
	for(i=2;i<=b;i++)
	{
		sum=0;
		if (i==c) break;
		x=c/i-1;
		y=c%i-1;
		if (x+1>=a) {x=a-1; y=0;}
        sum=x*(x+1)/2*(i-1)*i/2+y*(y+1)/2*(x+1)*(x+2)/2-y*(y+1)/2*x*(x+1)/2;
		if (ans<sum) ans=sum;
    } 
    for(i=2;i<=a;i++)
	{
		sum=0;
		if (i==c) break;
		x=c/i-1;
		y=c%i-1;
		if (x+1>=b) {x=b-1; y=0;}
        sum=x*(x+1)/2*(i-1)*i/2+y*(y+1)/2*(x+1)*(x+2)/2-y*(y+1)/2*x*(x+1)/2;
		if (ans<sum) ans=sum;
    } 
    printf("%lld",ans);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

6.連通塊

Description:

你應該知道無向圖的連通塊的數量,你應該知道如何求連通塊的數量。當你興奮與你的成就時,破壞王Alice拆掉了圖中的邊。當她發現,每刪去一條邊,你都會記下邊的編號,同時告訴她當前連通塊的個數。
然而,對邊編號簡直就是個悲劇,因為Alice為了刁難你,拆掉編號從l到r的邊,當然你需要做的事情就是求連通塊的個數。如果你答對了,Alice會把拆掉的邊裝好,迚行下一次破壞。如果你無法完成這個任務,Alice會徹底毀了你的圖。
進行完足夠多次之後,Alice覺得無聊,就玩去了,而你卻需要繼續做第三題。

Input:

第一行兩個整數n,m,表示點數和邊數。
之後m行每行兩個整數x,y,表示x與y之間有無向邊。(按讀入順序給邊編號,編號從1開始)
 一行一個整數k,表示Alice的破壞次數。
 之後k行,每行兩個整數l,r。 

Output:

k行,每行一個整數。 

Data Constraint:

對於30%的資料,n<=100,k<=10
對於60%的資料,k<=1000
對於100%的資料,n<=500,m<=10000,k<=20000,1<=l<=r<=m

Solutions:

記錄聯通塊的情況,我們一般會選擇並查集。

我們先來考慮 L 的情況,

如果全部 L 都是 1 這樣的做法是十分簡單的,不過需要離線操作:

根據 R 的大小排一次序,然後就開始連邊。

但是現在的 L 不一定是1,所以我們要想一下其他方法。

題目要求刪除 [l,r] 這個區間,所以我們需要連邊的部分分成了兩部分。

第一部分是1~l-1,第二部分是r+1~n

那我們可不可以預處理出1~l-1 和 r+1~n的聯通情況?

因為n很小,所以我們可以先預處理。

用f[i]表示連線1~i 的聯通情況,是一個並查集。

同理,用g[i]表示連線i~n 的聯通情況,也是一個並查集。

那麼對於查詢一個刪除區間[l,r],我們就需要將f[l]−1

和 g[r]+1

合併起來就可以了。

合併兩個並查集的時候,我們需要新建一個並查集,O(N)的時間來處理聯通情況。

Code:

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string.h>
#include <cmath>
#include <math.h>
using namespace std;

int n,t,k,l,r,ans,f1,f2,f3;
int f[10003][503],g[10003][503],m[503];
int x[10003],y[10003];

int get1(int y,int x)
{
    if(f[y][x]==x)return x;else f[y][x]=get1(y,f[y][x]);
    return f[y][x];
}

int get2(int y,int x)
{
    if(g[y][x]==x)return x;else g[y][x]=get2(y,g[y][x]);
    return g[y][x];
}

int get3(int x)
{
    if(m[x]==x)return x;else m[x]=get3(m[x]);
    return m[x];
}

int main()
{
    freopen("connect.in","r",stdin);
    freopen("connect.out","w",stdout);
    scanf("%d%d",&n,&t);

    for(int i=1;i<=n;i++)
    {
        f[0][i]=i;
        g[t+1][i]=i;
    }

    for(int i=1;i<=t;i++)
    {
        scanf("%d%d",&x[i],&y[i]);
        memcpy(f[i],f[i-1],sizeof(f[i]));
        f1=get1(i,x[i]);
        f2=get1(i,y[i]);
        f[i][f2]=f1;
    }

    for(int i=t;i;i--)
    {
        memcpy(g[i],g[i+1],sizeof(g[i]));
        f1=get2(i,x[i]);
        f2=get2(i,y[i]);
        g[i][f2]=f1;
    }

    scanf("%d",&k);
    for(int i=1;i<=k;i++)
    {
        scanf("%d%d",&l,&r);
        memcpy(m,f[l-1],sizeof(m)); 
        for(int j=1;j<=n;j++)
        {
            f1=get3(j);
            f2=get2(r+1,j);
            f3=get3(f2);
            if(f1!=f3)m[f3]=f1;
        }
        ans=0;
        for(int j=1;j<=n;j++)
            if(get3(m[j])==j)ans++;
        printf("%d\n",ans);
    }
}

7.Ede的新揹包問題

Description:

Input:

Output:

輸出 q行,第 i行輸出對於第 i個詢問的答案。 

Data Constraint:

Solutions:

思考一下假如題目沒有去掉某個玩偶,而只是給定q個詢問,然後改變m的值,我們會怎麼做?

我們會找到最大的m,然後跑一遍多重揹包就好了。

然後思考一下當時的轉移方程? f[i] = max(f[i], f[i - vi] + wi); 對嗎? 我們在學習揹包的時候得知,

空間上是可以只用一維的,那麼如果我們不去掉那一維呢?f[i][j] = max(f[i][j], f[i - 1][j - vi] + wi),f[i][j]表示

做完了前i個物品,可以獲得的最大價值。顯然此時的i其實是沒有影響的,所以我們省去,但如果不省去,我們

不就獲得了過程中最優嗎?看到這是不是想到怎麼做了,我們跑兩遍,正著來一遍,反著來一遍,然後直接統計

答案就好啦。

Code:

#include <cstdio>
#include <iostream>
#include <cstring>
#define N 2007
#define LL long long
using namespace std;
int n, q, w[N], v[N], c[N];
LL f[N][N], g[N][N];

int max(int a, int b)
{
    if (a > b)    return a;
    return b;
}

void Init()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d%d%d", &v[i], &w[i], &c[i]);
}

void Dp()
{
    for (int i = 1; i <= n; i++)
        for (int j = 1000; j >= 0; j--)
        {
            f[i][j] = f[i - 1][j];
                for (int k = 1; k <= c[i]; k++)
                    if (j >= k * v[i])    f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
                    else break;
        }
    memset(g, 0, sizeof(g));
    for (int i = n; i >= 1; i--)
        for (int j = 1000; j >= 0; j--)
        {
            g[i][j] = g[i + 1][j];
                for (int k = 1; k <= c[i]; k++)
                    if (j >= k * v[i])    g[i][j] = max(g[i][j], g[i + 1][j - k * v[i]] + k * w[i]);
                    else break;
        }
}

int main()
{
    Init();
    Dp();
    scanf("%d", &q);
    for (; q--; )
    {
        int m, ban;
        scanf("%d%d", &ban, &m);
        int ans = 0;
        for (int i = 0; i <= m; i++)
                ans = max(ans, f[ban][m - i] + g[ban + 2][i]);
        printf("%d\n", ans);
    }
}

8.模板串

Description:

科學家溫斯頓從資料庫中找到了一串相當長的字串。
他正試圖用一個模板串來重構這個字串。
他可以將模板串複製多份,通過合適的方式拼接起來,使得最終的串與原串一致。
如果兩個模板串互相覆蓋,那麼覆蓋的部分必須完全一致。
原串的所有位置必須被覆蓋到。
顯然,原串本身就是一個模板串。但為了節省成本,他想找到長度最短的模板串。

Input:

第一行一個僅由小寫字母構成的字串。

Output:

第一行一個整數,表示模板串的最小長度。 

Data Constraint:

設字串的長度為N。
Subtask1[20pts]:N<=100
Subtask2[30pts]:N<=25000
Subtask3[50pts]:N<=500000

Solutions:

首先我們知道模板串一定是字串的字首,由此我們想到的kmp的next陣列。
next[i]=k的意義是原串中[1..k]和[i-k+1..i]相等。
我們設fi表示將[1..i]都用模板串覆蓋了的模板串的長度。
顯然,fi可以等於i因為模板串就是自己。
第二種情況,f[i]=f[next[i]]
也就是說[i−nexti+1..i]的覆蓋方法與1..next[i]相同。
所以如果存在f[j]=f[next[i]](i−next[i]≤j)
那麼f[i]=f[next[i]]

Code:

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string.h>
#include <cmath>
#include <math.h>
#define N 500003
using namespace std;
char s[N],c;
int len,next[N],f[N],h[N];
void make(char* t,int len)
{
    memset(next,0,sizeof(next));
    int j=0;
    for(int i=2;i<=len;i++)
    {
        while(j>0 && t[j+1]!=t[i])j=next[j];
        if(t[i]==t[j+1])j++;
        next[i]=j;
    }
}
int main()
{
    c=getchar();
    while('a'>c && c>'z')c=getchar();
    s[1]=c;len=1;
    while('a'<=s[len] && s[len]<='z')s[++len]=getchar();
    len--;
    make(s,len);
    for(int i=1;i<=len;i++)
    {
        f[i]=i;
        if(h[f[next[i]]]>=i-next[i])f[i]=f[next[i]];
        h[f[i]]=i;
    }
    printf("%d\n",f[len]);
}

9.Clock Sequence

Description:

科學家溫斯頓定義了一個無限重複的數列:1234321234321234321……,並將其稱為時鐘序列。
他發現可以將數列人為分成幾段:
1, 2, 3, 4, 32, 123, 43, 2123, 432, 1234, 32123, ...
他又定義了新數列中第n項為Vn,這樣分組能夠滿足Vn的數字和等於n。例如,V2=2,V7=43,V11=32123。
請幫助他求出數列V的前n項和。

Input:

第一行一個正整數,表示n。

Output:

第一行一個整數,表示數列V前n項和對123454321取模後的值。 

Data Constraint:

設字串的長度為N。
Subtask1[20pts]:N<=40
Subtask2[20pts]:N<=1000
Subtask3[60pts]:N<=10^14

Solutions:

這題奇奇怪怪
40分是顯然的,直接n^2

暴力就可以得到
然後你需要打個表,就可以發現,數字是每15個一組的
比如說
1->1
2->2
3->3
4->4
5->32

15->1 23432
16->1 234321

30->123432 123432
31->1 234321 234321

46->1 234321 234321 234321

發現了什麼?
每個數都是AB…的形式的
比如說每組的第一個數,就是1、16、31、46等
A=1,B=234321
16時是AB
31時是ABB
46時是ABBB
發現這個規律後,就可以打一個15的表,後面的數用矩陣乘法求出
當然,也可以用數學方法如等比數列的方式,但是由於mo數不是質數,要用exgcd,懶得打,而且矩陣乘法方便很多

Code:

#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define mo 123454321
#define ll long long
#define M 1000000
using namespace std;
ll A[16]={0,1,2,3,4,32,123,43,2123,432,1234,32123,43212,34321,23432,123432};
ll B[16]={0,234321,343212,432123,321234,123432,432123,212343,432123,123432,321234,432123,343212,234321,123432,123432};
ll a[3][3]={M,0,M,1,1,1,0,0,1},b[3][3],c[3][3];
ll n,ans=0;
void cl()
{
    fo(i,0,2) fo(j,0,2) c[i][j]=a[i][j],a[i][j]=0;
}
void ch()
{
    cl();
    fo(i,0,2) fo(j,0,2) fo(k,0,2) a[i][k]=(a[i][k]+c[i][j]*c[j][k])%mo;
}
void ch2()
{
    cl();
    fo(i,0,2) fo(j,0,2) fo(k,0,2) a[i][k]=(a[i][k]+c[i][j]*b[j][k])%mo;
}
void mi(ll x)
{
    if(x<=1) return;
    fo(i,0,2) fo(j,0,2) b[i][j]=a[i][j];
    mi(x/2);
    ch();
    if(x%2==1) ch2();
}
int main()
{
    scanf("%lld",&n);
    if(n<15) 
    {
        fo(i,1,n) ans=(ans+A[i])%mo;
        printf("%lld\n",ans);return 0;
    }
    ll jy1=n/15,jy2=n%15;
    mi(jy1-1);
    if(jy1==1)
    {
        memset(a,0,sizeof(a));
        a[0][0]=a[1][1]=a[2][2]=1;
    }
    fo(i,0,2) fo(j,0,2) b[i][j]=a[i][j];
    fo(i,1,15)
    {
        memset(a,0,sizeof(a));
        a[0][0]=A[i];a[0][1]=B[i];a[0][2]=A[i];
        ch2();
        ans=(ans+a[0][2])%mo;
        if(i<=jy2) ans=(ans+a[0][0]*M+a[0][1])%mo;
    }
    printf("%lld\n",ans);
}

10.硬幣遊戲

Description:

FJ的奶牛喜歡玩硬幣遊戲,所以FJ發明了一個新的硬幣遊戲。一開始有N(5<=N<=2,000)個硬幣堆成一疊,從上往下數第i個硬幣有一個整數值C_i(1<=C_i<=100,000)。
兩個玩家輪流從上倒下取硬幣,玩家1先取,可以從上面取1個或2個硬幣,下一輪的玩家可以取的硬幣數量最少為1個,最多為上一個玩家取的數量的2倍,硬幣全部取完比賽結束。
已知玩家2絕頂聰明,會採用最優策略,現在請你幫助玩家1,使得玩家1取得的硬幣值的和最大。

Input:

第一行輸入N
第二至N+1行每行輸入一個整數C[i]

Output:

輸出玩家1能獲得的最大值。 

Data Constraint:

不知道為什麼沒有

Solutions:

DP: 1.狀態:建立一個二維的狀態(i,j)說明拿硬幣的權力到達其中一名玩家時,桌面上還剩下編號為1~i(倒序,1為最底下的) 的硬幣,

上一名玩家拿走了j枚硬幣。

2.下一步的狀態:那麼這一個玩家在這一輪可以選擇拿走1,2,3,4…2*j枚硬幣,而他所能獲得的最大硬幣面值就是1~i所有的硬幣面

值之和減去他完成此次操作後(設他取走了k枚硬幣)到達狀態(i-k,k)的另一名玩家所能獲得的最大硬幣數。

3.狀態的轉移:可是因為k的取值範圍很大,所以不能直接列舉,不難發現(i,j-1)和(i,j)的狀態只相差兩種選擇的可能,即下一步取走

2*j-1或2*j個硬幣的這兩種,只需要再比較一次這兩種狀態即可。

狀態轉移方程:dp[i][j]=max(dp[i][j],sum[i]-dp[i-k][k],sum[i]-dp[i-k-1][k+1])(k==2\j-1);

4.答案:答案則是在剩下1~n枚硬幣時(初始狀態)的dp[n][1](下一步可以選擇1枚或兩枚硬幣)了。

Code:

# include<iostream>
# include<cstring>
# include<cstdio>
# include<vector>
# include<cctype>
# include<string>
# include<ctime>
# include<cmath>
# include<stack>
# include<queue>
# include<list>
# include<set>
# include<map>
# define ll long long
# define ull unsigned long long
using namespace std;
int n,sum[2001],c[2001],dp[2001][2000];
int main()
{
    cin>>n;
    for(int i=n;i>=1;i--)cin>>c[i];
    for(int i=1;i<=n;i++)sum[i]+=sum[i-1]+c[i];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            dp[i][j]=dp[i][j-1];
            int k=2*j-1;
            if(k<=i)dp[i][j]=max(dp[i][j],sum[i]-dp[i-k][k]);
            k+=1;
            if(k<=i)dp[i][j]=max(dp[i][j],sum[i]-dp[i-k][k]);
        }
    cout<<dp[n][1]<<endl;
    return 0;
}