AGC048 部分题解

A

先判断 S S S 字典序是否大于atcoder,是就输出 0 0 0

否则往后面找到第一个不为a的字母,移到第一位即可。假如这个字母字典序比t还大,那么移到第二位就够了。

代码如下:

#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 100010
int T,n;
char s[maxn];
char s2[9]={' ','a','t','c','o','d','e','r',0};

int main()
{
	cin>>T;while(T--)
	{
		cin>>s+1;n=strlen(s+1);
		int v=0;
		for(int i=1;i<=min(n,7);i++){
			if(s[i]>s2[i]){v=1;break;}
			if(s[i]<s2[i]){v=-1;break;}
		}
		if(v==1||(!v&&n>7)){cout<<"0\n";continue;}
		int st=1;while(st<=n&&s[st]=='a')st++;
		if(st>n)cout<<"-1\n";
		else{
			if(st>1&&s[st]>'t')cout<<st-2<<"\n";
			else cout<<st-1<<"\n";
		}
	}
}

B

先假设全部位置都用圆括号,然后考虑将一些位置变成方括号。

假设一对方括号的位置为 x , y x,y x,y,那么 y − x + 1 y-x+1 yx+1 一定是偶数,即 x , y x,y x,y 的奇偶性一定不同。

然后可以发现,假如选出的变成方括号的位置集合为 X = { x 1 , x 2 , . . . , x k } X=\{x_1,x_2,...,x_k\} X={x1,x2,...,xk},那么 X X X 内奇数和偶数各占一半。而奇数和偶数各占一半的集合,一定对应一个合法的括号序列。

一种简单的构造方案是:将 X X X 内的元素排序,每次找到一对相邻的并且奇偶性不同的 x i , x i + 1 x_i,x_{i+1} xi,xi+1(显然如果 X X X 不为空那么一定存在这样的一对数),将 [ x i , x i + 1 ] [x_i,x_{i+1}] [xi,xi+1] 区间的括号序列变成[()()...()()],内部已经被操作过的区间不变,然后将这一对数从 X X X 中删除。重复这个操作直到 X X X 为空,这样这个序列就造好了。

现在的问题是:选一些位置变成方括号,并且选出来的位置奇数和偶数各占一半。这个贪心即可,将 B i − A i B_i-A_i BiAi 进行排序,奇数位和偶数位的分别排,然后每次选最大的两个,看看是否大于 0 0 0,是就选上。

代码如下:

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 100010
 
int n,a[maxn];
vector<int> b[2];
long long ans=0;
 
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		ans+=a[i];
	}
	for(int i=1,x;i<=n;i++){
		scanf("%d",&x);
		b[i&1].push_back(x-a[i]);
	}
	for(int i=0;i<2;i++)sort(b[i].begin(),b[i].end());
	for(int i=n/2-1;i>=0;i--){
		int val=b[0][i]+b[1][i];
		if(val<=0)break;
		ans+=val;
	}
	printf("%lld",ans);
}

C

考虑相邻两只企鹅之间的空隙长度,记为 c i = A i + 1 − A i − 1 c_i=A_{i+1}-A_i-1 ci=Ai+1Ai1,那么企鹅的一次移动等价于使 j = i + 1 / i − 1 j=i+1/i-1 j=i+1/i1,让 c j + = c i , c i : = 0 c_j+=c_i,c_i:=0 cj+=ci,ci:=0

d i = B i + 1 − B i − 1 d_i=B_{i+1}-B_i-1 di=Bi+1Bi1,那么对于 d i > 0 d_i>0 di>0 i i i,这段空隙肯定等于 c c c 的一个区间 [ l , r ] [l,r] [l,r] 的和,对于 i > j i>j i>j d i > 0 , d j > 0 d_i>0,d_j>0 di>0,dj>0,那么 j j j 对应的区间一定在 i i i 对应的区间前面。即对应的区间是有单调性的,并且这些区间不可能有交集,于是扫一遍就能得到这些区间。

而要将一个区间 [ l , r ] [l,r] [l,r] 的值全部累加到 i i i 位置,需要的操作次数为 max ⁡ ( i − l , 0 ) + max ⁡ ( r − i , 0 ) \max(i-l,0)+\max(r-i,0) max(il,0)+max(ri,0),这个操作次数的总和就是答案,虽然需要考虑不同 i i i 之间操作的影响,但是由于区间有单调性,所以这个下限总是可以达到的,方案也不难构造。

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 100010

int n,l,a[maxn],b[maxn],c[maxn],d[maxn];
long long ans=0;

int main()
{
	scanf("%d %d",&n,&l);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);a[n+1]=l+1;
	for(int i=1;i<=n;i++)scanf("%d",&b[i]);b[n+1]=l+1;
	for(int i=0;i<=n;i++)c[i]=a[i+1]-a[i]-1;
	for(int i=0;i<=n;i++)d[i]=b[i+1]-b[i]-1;
	for(int i=0,j=0;i<=n;i++)if(d[i]){
		while(c[j]==0)j++;
		int l=j,s=0;
		while(j<=n&&s<d[i])s+=c[j++];
		if(s!=d[i])return printf("-1"),0;
		ans+=max(i-l,0)+max(j-1-i,0);
	}
	printf("%lld",ans);
}

D

有一个性质:一个人每次要么拿一个,那么拿一堆,不可能拿 k ∈ ( 1 , a l ) k\in(1,a_l) k(1,al) 个。如果拿了 k k k 个,那么下一次就只能拿 [ 1 , a l − k ] [1,a_l-k] [1,alk] 个,如果拿 1 1 1 个,那么下一次可以拿 [ 1 , a l − 1 ] [1,a_l-1] [1,al1] 个,显然后者的决策范围包含前者,所以前者一定不优。

然后考虑 dp \text{dp} dp,设 l i , j l_{i,j} li,j 表示 两人只用 [ i , j ] [i,j] [i,j] 范围内的石子进行游戏,左方先手,且可以任意修改 a i a_i ai 的初值,则 a i a_i ai 至少为多少可以使左方必胜。

类似的,定义出 r i , j r_{i,j} ri,j:右方先手,则 a j a_j aj 至少为多少可以使右方必胜。

r r r 的转移和 l l l 类似,这里只说 l l l 的。

转移有两种:取一个或取一堆。如果 A j < r i + 1 , j A_j<r_{i+1,j} Aj<ri+1,j,那么左方直接取一堆,右方就变成了必败态,即左方无论 a i a_i ai 是多少都必胜,让 l i , j = 1 l_{i,j}=1 li,j=1

否则,两个人肯定轮流一个一个取,直到某一方发现,自己取完之后对面必败,那么他就肯定会选择取完。

当左方取了 l i , j − l i , j − 1 + 1 l_{i,j}-l_{i,j-1}+1 li,jli,j1+1 个时,右方直接取完,则右方胜利。当右方取了 a j − r i + 1 , j + 1 a_j-r_{i+1,j}+1 ajri+1,j+1 个时,左方直接取完,则左方胜利。要使左方必胜,则需要满足:
l i , j − l i , j − 1 + 1 > a j − r i + 1 , j + 1 l i , j > a j − r i + 1 , j + l i , j − 1 l_{i,j}-l_{i,j-1}+1>a_j-r_{i+1,j}+1\\ l_{i,j}>a_j-r_{i+1,j}+l_{i,j-1} li,jli,j1+1>ajri+1,j+1li,j>ajri+1,j+li,j1

所以 l i , j l_{i,j} li,j 的最小值为 a j − r i + 1 , j + l i , j − 1 + 1 a_j-r_{i+1,j}+l_{i,j-1}+1 ajri+1,j+li,j1+1

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 110
#define ll long long

int T,n;
ll a[maxn];
ll l[maxn][maxn],r[maxn][maxn];

int main()
{
	scanf("%d",&T);while(T--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			scanf("%lld",&a[i]);
			l[i][i]=r[i][i]=1;
		}
		for(int len=2;len<=n;len++){
			for(int i=1;i+len-1<=n;i++){
				int j=i+len-1;
				if(a[j]<r[i+1][j])l[i][j]=1;
				else l[i][j]=a[j]-r[i+1][j]+l[i][j-1]+1;
				if(a[i]<l[i][j-1])r[i][j]=1;
				else r[i][j]=a[i]-l[i][j-1]+r[i+1][j]+1;
			}
		}
		if(a[1]>=l[1][n])printf("First\n");
		else printf("Second\n");
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值