[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;
}

猜你喜欢

转载自blog.csdn.net/qq_40155097/article/details/81902022