CSP-S2020游记&题解

倒数第二次参加 OI 比赛,首先还是要感谢这次的承办方,比赛环境完全达到了 NOI 的水准(就是学校太远了…)
总的来说,题目仍然着重考察基本功,就是我太蠢了,不是很会做而已…
不过也仅仅只是 NOIP2020 的资格赛…这次考的不理想,说不定是在给下次积攒人品呢

Part 1 游记

day -1

考试前一天去学校试机,离我们学校实在是太远了,不堵车都要坐一个多小时的车…
环境不错,很有考试氛围。虽然总共被坑了530元但是还是要点个赞。
回去的时候堵车,坐了两个多小时,突然对下个月早上 8:30 开始考的 NOIP 有一些担忧。

day 1

大约早上 10 点钟就出发了,到学校正好赶上吃午饭。发现我们学校的食堂跟别的学校比起来,无论是环境还是菜品都要质朴很多呢…
中午的时候围观了 CSP-J 2020 的试题,没有仔细看,只听说 T3 很难,其余三道都是水题。似乎在暗示今天下午的题目可能也会存在难度明显倒序的情况。然后就开始睡午觉(果然又没睡着,不过我都习惯了…)。
奇怪的是下午 2:20 进入考场就能看到题目了,只是当时还在写准备程序,没有注意到这件事情…
大概 2:26 的看到 T1 ,有些惊讶,今年的出题人和我一个德行的吗,想起了去年出的一套 CSP-J 模拟赛,其中的 T1 也是一道很类似的模拟题目。不过这题规则看起来似乎还要复杂一些,而且 r i r_i ri 的值特别的大(也还好吧,至少没有问你什么两个日期之间相差多少天之类的恶心问题…)。
我有点不相信这题是整场考试最简单的题目,于是赶紧去看 T2 ,仔细思考了一下发现果然要简单的多,不过发现 unsigned long long 又卷土重来了。于是花了大概 40min 的时间打代码+检查细节,发现答案还有可能等于 2 64 2^{64} 264,需要单独打表输出(想到这里真就非常想好好感谢一下T1T2的出题人),于是单独判掉了。
然后开始写 T1 ,写了大概 45min 左右,调试阶段异常的顺利,过了编译就把大样例过了…不过发现大样例只存在在 1582 1582 1582 年以后的情况, 1582 1582 1582 年以前的情况还需要好好检查,再花了大概 10min 左右的时间检查,果然找出来一个错误(有些惊险),然后就暂时放掉 T1 去看后面的题目了。
写完 T1,T2 就已经花了刚好 2h 的时间了,感觉后面的题有点来不及了…
然后看 T3 ,感觉用上逆元之类的技巧还比较好做,但是看到数据范围还可以整体乘上零就有点不好处理了…[这其实也是这次CSP最主要的失误,当时没有仔细的思考用逆元处理的具体方法,就直接给 T3 定了一个不可做的数据结构神仙题的标签]。
怀疑后面两道题目又来难度顺序交换,于是看了 T4 。感觉是一道很神秘的题目,我也不知道这条蛇会有多聪明是吧,但是觉得和 NOIP2016 蚯蚓 这道题目非常的相似,可能也是一道维护优先队列然后再用两个队列优化的题目?想了一些比较显然的性质,似乎 70 % 70\% 70% 的数据很好拿分,只需要一个 multiset 就能够维护了?试着写完之后发现大样例的一些答案多了 1,让人迷惑…然后发现减一的都是偶数,但是仔细思考了一下感觉有点问题,似乎这样是有问题的…然后就有点不会做了…看了看时间发现又过了 1h ,感觉自己有点凉凉了…(其实当时已经知道70分做法了,但是我没有多大把握,以为自己能在 T3 骗到一个满意的成绩…)
于是什么都没有想,开始尽可能的打 T3 的暴力分,结果到考试结束也只写完了 20 + 20 = 40 20+20=40 20+20=40 的部分分。最后还是检查了细节,应该没有什么问题…
期望得分: 100 + 100 + 40 + 20 = 260 100+100+40+20=260 100+100+40+20=260
出了考场发现一堆人切 T3…后来才发现和某学校出的模拟赛思路基本一致…而且由于暴力常数小导致 O ( n 2 ) O(n^2) O(n2) 暴力能跑 75 分???(写到这里也想好好感谢一下T3的出题人, 20000 20000 20000 O ( n 2 ) O(n^2) O(n2) 的数据,真就不是逆向区分)不用说了,NOIP 2020 大概率退役了…

day inf

今天勇敢的去看了成绩,发现自己 T2 带 log 的算法跑的极慢,即使在 CCF 的神机上也不一定不会卡常数…唉…只能看命了。T3 由于自己的智障又被卡了 10 10 10 分。
预计得分: 100 + [ 90 , 100 ] + 30 + 20 = [ 240 , 250 ] 100+[90,100]+30+20=[240,250] 100+[90,100]+30+20=[240,250]
CQ 的分数分布实在是太密集了…差一点点分就差了好多名…不过 NOIP 分数占比还算小,还是有翻盘的可能的…
确实实力不行,活该退役。
可以明显看的出来,今年 CSP 的出题人还是去年省选那些出题人,题目非常注重细节而不是思维量,在一个4个小时四道题目的比赛中,在这的确是一个非常棘手的一个问题。而 NOIP 虽然会换掉一些出题人,但大的方向还是不会变的。接下来这最后的一个月应该好好攻克时间分配和细节问题。
出分了, 100 + 100 + 30 + 20 = 250 100+100+30+20=250 100+100+30+20=250
如果当时直接乱搞远远不止这么一点分…我把 CCF 的数据想的太强了…唉…
还好不是 NOIP ,至少还有时间改正吧…

day inf+

今天终于出了榜单,算了算除去卡名额和初中的排在了 21…确实这次考的不理想(和这次题目非常奇怪,数据过水有关系),如果是 NOIP 的话很危险,可能就处在退役和不退役之间这种状态吧。
总之,也不是完全没有希望。当然真的希望这次不要再出什么模拟题细节题了…出在这种 4h 4题的比赛完全就是在比谁模拟写的快。
其实这次的失败完全就在于自己提前放弃了马上就要得到 70 分的 T4 ; T3 没想到数据这么水,没有掌握数据分治的的技巧(其实和前两道题目有着很大的关系)。

Part 2 题解

倒数第二次写比赛题解了。
顺便也把 J 组的题目做了。

[CSP-J 2020]优秀的拆分

二进制拆分模板题?
复杂度: O ( l o g 2 n ) O(log_2 n) O(log2n)

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
#include<map>
#include<cstring>
using namespace std;

#define LL long long
#define DB double
#define Pr pair<int,int>
#define X first
#define Y second
#define MAXN 10000000
#define INF 1000000000

int n;

LL read(){
	LL x=0,F=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')F=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*F;
}

int main(){
	freopen("power.in","r",stdin);
	freopen("power.out","w",stdout);
	n=read();
	if(n%2)puts("-1");
	else{
		for(int i=23;i>=1;i--)
		if((n>>i)&1)printf("%d ",(1<<i));
	}
}

[CSP-J 2020]直播获奖

看到"每个选手的成绩均为不超过 600 600 600 的非负整数"这句话就做完了。
复杂度: O ( 600 n ) O(600n) O(600n)

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
#include<map>
#include<cstring>
using namespace std;

#define LL long long
#define DB double
#define Pr pair<int,int>
#define X first
#define Y second
#define MAXN 100000
#define INF 1000000000

int n,w,cnt[650],a[MAXN+5];

LL read(){
	LL x=0,F=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')F=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*F;
}

int main(){
	freopen("live.in","r",stdin);
	freopen("live.out","w",stdout);
	n=read(),w=read();
	for(int i=1;i<=n;i++){
		a[i]=read();cnt[a[i]]++;
		int t=max(1,i*w/100),sorce=0;
		for(int j=600;j>=0;j--)
		if(cnt[j]>=t){sorce=j;break;}
		else t-=cnt[j];
		printf("%d ",sorce);
	}
}

[CSP-J 2020]表达式

居然是我之前打算投给 CF 的原题…投题数 − − --
我们先模拟出这颗表达式树,然后我们考虑先求出每个子树的答案,再根据答案确定左右子树中的哪一个答案改变会使这个子树的答案也改变,标记每个被遍历到的儿子,最后集体回答即可。
复杂度: O ( n + q ) O(n+q) O(n+q)

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
#include<map>
#include<cstring>
using namespace std;

#define LL long long
#define DB double
#define Pr pair<int,int>
#define X first
#define Y second
#define MAXN 1000000
#define INF 1000000000

char s[MAXN+5];
int n,q,op[MAXN+5],lch[MAXN+5],rch[MAXN+5],flag[MAXN+5];
int tmp[MAXN+5],cnt,id,val[MAXN+5],f[MAXN+5];

void readexpr(){
	cnt=1;id=MAXN/10;
	while(1){
		char c=getchar();
		if(c=='\n')break;
		else if(c==' ')cnt++;
		else if(c>='0'&&c<='9')tmp[cnt]=tmp[cnt]*10+c-'0';
		else if(c=='!')op[++id]=3,lch[id]=tmp[cnt-1],tmp[cnt-1]=id,cnt--;
		else if(c=='&'){
			//printf("%d %d\n",tmp[cnt-2],tmp[cnt-1]);
			op[++id]=2,lch[id]=tmp[cnt-2],rch[id]=tmp[cnt-1],cnt-=2,tmp[cnt]=id,tmp[cnt+1]=0;
		}
		else if(c=='|')op[++id]=1,lch[id]=tmp[cnt-2],rch[id]=tmp[cnt-1],cnt-=2,tmp[cnt]=id,tmp[cnt+1]=0;
	}
}

LL read(){
	LL x=0,F=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')F=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*F;
}

int dfs(int x){
	if(!op[x])return f[x]=val[x];
	if(op[x]==3)return f[x]=!dfs(lch[x]);
	if(op[x]==2)return f[x]=dfs(lch[x])&dfs(rch[x]);
	if(op[x]==1)return f[x]=dfs(lch[x])|dfs(rch[x]);
	return -1;
}
void dfs2(int x){
	if(!op[x])flag[x]=1;
	if(op[x]==3)dfs2(lch[x]);
	if(op[x]==2){
		if(f[x]==0){
			if(f[lch[x]]^f[rch[x]]){
				if(!f[lch[x]])dfs2(lch[x]);
				if(!f[rch[x]])dfs2(rch[x]);
			}
		}
		else dfs2(lch[x]),dfs2(rch[x]);
	}
	if(op[x]==1){
		if(f[x]==1){
			if(f[lch[x]]^f[rch[x]]){
				if(f[lch[x]])dfs2(lch[x]);
				if(f[rch[x]])dfs2(rch[x]);
			}
		}
		else dfs2(lch[x]),dfs2(rch[x]);
	}
}

int main(){
	freopen("expr.in","r",stdin);
	freopen("expr.out","w",stdout);
	readexpr();
	n=read();
	for(int i=1;i<=n;i++)val[i]=read();
	f[id]=dfs(id);
	dfs2(id);
	q=read();
	while(q--){
		int pos=read();
		printf("%d\n",flag[pos]^f[id]);
	}
}

[CSP-J 2020]方格取数

经典 dp 题目,在每行开两个 tmp 数组辅助转移即可。
复杂度: O ( n m ) O(nm) O(nm)

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
#include<map>
#include<cstring>
using namespace std;

#define LL long long
#define DB double
#define Pr pair<int,int>
#define X first
#define Y second
#define MAXN 1000
#define INF 10000000000000000

int n,m,a[MAXN+5][MAXN+5];
LL f[MAXN+5][MAXN+5],f1[MAXN+5],f2[MAXN+5],ans;

LL read(){
	LL x=0,F=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')F=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*F;
}

int main(){
	freopen("number.in","r",stdin);
	freopen("number.out","w",stdout);
	n=read(),m=read();
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		a[i][j]=read();
	f[1][0]=0;
	for(int i=2;i<=n;i++)f[i][0]=-INF;
	for(int j=1;j<=m;j++){
		for(int i=1;i<=n;i++)f1[i]=f2[i]=f[i][j-1]+a[i][j];
		f1[0]=-INF;
		for(int i=1;i<=n;i++)f1[i]=max(f1[i],f1[i-1]+a[i][j]);
		f2[n+1]=-INF;
		for(int i=n;i>=1;i--)f2[i]=max(f2[i],f2[i+1]+a[i][j]);
		for(int i=1;i<=n;i++)f[i][j]=max(f1[i],f2[i]);
	}
	printf("%lld",f[n][m]);
}

[CSP-S 2020]儒略日

恶心模拟题…
分成 1582 1582 1582 年以前, 1582 1582 1582 年, 1582 1582 1582 年以后按题意模拟,但是这样只能够得到 40 40 40 分的好成绩…
1582 1582 1582 年以前,我们每四年一个周期的日数是确定的;在 1582 1582 1582 年以后,我们每 400 400 400年一个周期的日数是确定的。这样我们就可以简单的用循环节来快速跳到一个合适的位置再计算。
复杂度: O ( 400 q ) O(400q) O(400q)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;

#define LL long long
#define DB double
#define Pr pair<int,int>
#define X first
#define Y second
#define MAXN 200000
#define INF 1000000001
#define MOD 1000000007

LL read(){
	LL x=0,F=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')F=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*F;
}

int Q;LL r;
int t1[]={31,28,31,30,31,30,31,31,30,31,30,31};
int t2[]={31,29,31,30,31,30,31,31,30,31,30,31};

void print(int y,LL d,int type){
	int m=0;
	if(y==1582){
		int F=1;
		for(int i=0;i<9;i++){
			if(d>=t1[i])d-=t1[i];
			else {F=0;break;}
			m++;
		}
		if(F){
			if(d>=4){
				d+=10;
				for(int i=9;i<12;i++){
					if(d>=t1[i])d-=t1[i];
					else break;
					m++;
				}
			}
		}
	}
	else if(type){
		for(int i=0;i<12;i++){
			if(d>=t2[i])d-=t2[i];
			else break;m++;
		}
	}else{
		for(int i=0;i<12;i++){
			if(d>=t1[i])d-=t1[i];
			else break;m++;
		}
	} 
	if(y<=0)printf("%lld %d %d BC\n",d+1,m+1,-y+1); 
	else printf("%lld %d %d\n",d+1,m+1,y);
}

bool check_y(int y){
	if(y%400==0)return 1;
	if(y%100==0)return 0;
	if(y%4==0)return 1;
	return 0; 
} 

void solve(){
	int ny=-4712,tot=0;
	for(int i=-4712;i<=-4712+3;i++)
	if(i%4==0)tot+=366;
	else tot+=365;
	LL py=min(r/tot,(1580LL-ny)/4);
	ny+=py*4,r-=py*tot;
	for(int i=ny;i<=1581;i++){
		if(i%4==0){
			if(r>=366)r-=366;
			else {print(i,r,1);return ;} 
		}else{
			if(r>=365)r-=365;
			else {print(i,r,0);return ;}
		}
	}
	if(r>=355)r-=355;
	else {print(1582,r,0);return ;}
	ny=1583,tot=0;
	for(int i=1583;i<=1583+399;i++)
	if(check_y(i))tot+=366;
	else tot+=365;
	ny+=(r/tot)*400,r%=tot;
	while(1){
		if(check_y(ny)){
			if(r>=366)r-=366;
			else {print(ny,r,1);return ;} 
		}else{
			if(r>=365)r-=365;
			else {print(ny,r,0);return ;}
		}
		ny++;
	}
	puts("-1");
	return ;
}

int main(){
	freopen("julian.in","r",stdin);
	freopen("julian.out","w",stdout);
	Q=read();
	while(Q--){
		r=read();
		solve();
	}
}

[CSP-S 2020]动物园

因为“不同的数位对应的饲料不会有交集”所以我们可以直接很方便的计算出可以调整的二进制数位数个数 c n t cnt cnt ,那么答案就是 2 c n t − n 2^{cnt}-n 2cntn
但是这题还需要注意使用unsigned long long c n t = 64 , n = 0 cnt=64,n=0 cnt=64n=0 的情况答案为 2 64 2^{64} 264 同样需要特判。
复杂度: O ( n ) O(n) O(n)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#include<queue>
#include<map>
using namespace std;

#define LL unsigned long long
#define DB double
#define Pr pair<int,int>
#define X first
#define Y second
#define MAXN 200000
#define INF 1000000001
#define MOD 1000000007

LL read(){
	LL x=0,F=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')F=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*F;
}

int n,m,c,k,cnt,vis[64];
LL pt,bt[65];
map<int,int> D;
vector<int> G[MAXN+5];

int main(){
	freopen("zoo.in","r",stdin);
	freopen("zoo.out","w",stdout);
	n=read(),m=read(),c=read(),k=read();
	for(int i=1;i<=n;i++)pt|=read();
	bt[0]=1;
	for(int i=1;i<=64;i++)bt[i]=bt[i-1]*2;
	for(int i=1;i<=m;i++){
		int p=read(),q=read();
		vis[p]=1;
	}
	for(int i=0;i<k;i++){
		if((!(pt&bt[i]))&&vis[i]);
		else cnt++;
	}
	if(cnt==64&&n==0)printf("18446744073709551616");
	else printf("%llu",bt[cnt]-n);
}

[CSP-S 2020]函数调用

这题其实不难,即使可以乘以零也很好解决。主要就是考试时间太紧张了。
首先考虑 不含第 2 类函数或不含第 1 类函数 这个部分分,注意到此时和操作的顺序无关,我们可以直接在每个节点上打上一个标记,然后拓扑排序一遍传递这个标记,将这些标记全部下放到叶节点一起处理。
那么对于两类函数都存在的情况,我们发现我们只需要知道每种操作它再之后被乘上了多少个数就能够计算贡献。我们首先预处理 p r d x prd_x prdx 表示调用一次 x x x 函数会乘上多少的数。然后我们将这 q q q 个调用从后往前处理,我们在对应点上加上一个权值为调用之后的函数的乘积的标记,最后我们倒着用拓补排序将这些标记全部下方到叶节点,统一计算贡献即可。
复杂度: O ( n + m ) O(n+m) O(n+m)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
using namespace std;

#define LL long long
#define DB double
#define Pr pair<int,int>
#define X first
#define Y second
#define MAXN 1000000
#define INF 1000000001
#define MOD 998244353

LL read(){
	LL x=0,F=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')F=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*F;
}

int add(int a,int b){return (a+b>=MOD)?a+b-MOD:a+b;}
int dec(int a,int b){return (a-b<0)?a-b+MOD:a-b;}
int mul(int a,int b){return (1LL*a*b)%MOD;}
void upd(int &a,int b){a=add(a,b);}

int m,n,q,a[MAXN+5],vis[MAXN+5],prd[MAXN+5];
int typ[MAXN+5],pos[MAXN+5],val[MAXN+5],f[MAXN+5];
int dp[MAXN+5],du[MAXN+5],tag[MAXN+5],res,suf;
vector<int> G[MAXN+5];
queue<int> Q;

void dfs(int x){
	vis[x]=1,prd[x]=(typ[x]==2)?val[x]:1;
	for(int i=0;i<G[x].size();i++){
		int v=G[x][i];
		if(!vis[v])dfs(v);
		prd[x]=mul(prd[x],prd[v]);
	}
}

int main(){
	freopen("call.in","r",stdin);
	freopen("call.out","w",stdout);
	m=read();
	for(int i=1;i<=m;i++)a[i]=read();
	n=read();
	for(int i=1;i<=n;i++){
		int op=read();typ[i]=op;
		if(op==1)pos[i]=read(),val[i]=read();
		else if(op==2)val[i]=read();
		else{
			int tot=read();
			while(tot--){
				int x=read();du[x]++;
				G[i].push_back(x);
			}
		}
	}
	for(int i=1;i<=n;i++)if(!vis[i])dfs(i);
	q=read();
	for(int i=1;i<=q;i++)f[i]=read();
	suf=1;
	for(int i=q;i>=1;i--){
		if(typ[f[i]]==1)upd(dp[f[i]],suf);
		else if(typ[f[i]]==2)suf=mul(suf,prd[f[i]]);
		else upd(dp[f[i]],suf),suf=mul(suf,prd[f[i]]);
	}
	for(int i=1;i<=n;i++)
	if(!du[i])Q.push(i);
	while(!Q.empty()){
		int x=Q.front();Q.pop();
		if(typ[x]==1)upd(tag[pos[x]],mul(dp[x],val[x]));
		LL tmp=dp[x];
		reverse(G[x].begin(),G[x].end());
		for(int i=0;i<G[x].size();i++){
			int v=G[x][i];
			upd(dp[v],tmp),tmp=mul(tmp,prd[v]);
			du[v]--;if(!du[v])Q.push(v);
		}
	}
	for(int i=1;i<=m;i++)printf("%d ",add(mul(a[i],suf),tag[i]));
}

[CSP-S 2020]贪吃蛇

我真是一个人才,在马上就调出 70 分做法的时候把这道题弃掉了。

算法1:70%

首先显然每条蛇如果吃掉最小的蛇之后不会被成为最小的蛇被吃掉,它肯定会选择继续游戏。
但是这样模拟出来一些会和大样例的答案相差 1 1 1 ,我们再仔细思考,发现即使这条蛇吃掉最小蛇后成为最小的蛇,如果接下来的最大蛇不敢吃掉这条蛇,这条蛇就会选择吃掉它。
那么接下来的最大蛇为什么不敢吃掉它呢?是因为再接下来的蛇敢吃掉它。
我们发现这是一个交替的情况。我们只需要找到之后最近的不会成为最小蛇的蛇,我们就可以直接根据这一段的奇偶性来判断最开始的那一条蛇是敢吃还是不敢吃。
接下来我们就可以使用 set 来模拟这个过程了。
复杂度: O ( T n log ⁡ n ) O(Tn\log n) O(Tnlogn)
开了 O2 直接就过了。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
using namespace std;

#define LL unsigned long long
#define DB double
#define Pr pair<int,int>
#define X first
#define Y second
#define MAXN 1000000
#define INF 1000000001
#define MOD 1000000007

LL read(){
	LL x=0,F=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')F=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*F;
}

int T,a[MAXN+5],n,k;
set<Pr> S;
set<Pr>::iterator it;

void solve(){
	int F=0,ans=1,tg=0;
	while(!S.empty())S.erase(S.begin());
	for(int i=1;i<=n;i++)S.insert(Pr(a[i],i));
	for(int i=1;i<n;i++){
		int num=n-i,et=1;
		Pr nx,mx,mn,cmn;
		mn=*S.begin();S.erase(S.begin());
		cmn=*S.begin();
		it=S.end(),it--;mx=*it;S.erase(it);
		nx.X=mx.X-mn.X,nx.Y=mx.Y;
		if(num>=2&&nx<cmn)et=0;
		S.insert(nx);
		if(!F){
			if(!et)F=1,ans=num+1;
		}
		else{
			if(!et)tg^=1;
			else break;
		}
	}
	if(F)printf("%d\n",ans-tg);
	else printf("%d\n",ans);
}

int main(){
	freopen("snakes.in","r",stdin);
	freopen("snakes.out","w",stdout);
	T=read()-1,n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	solve();
	while(T--){
		k=read();
		for(int i=1;i<=k;i++){
			int u=read(),v=read();
			a[u]=v;
		}
		solve();
	}
}

算法2:100%

很显然我们需要像蚯蚓这道题目一样用若干个队列来保证它的单调性。
考虑在本题中我们实际上经历了两个过程:

  • 当没有任何蛇吃完最小蛇后成为最小蛇,能够发现的是此时我们一定会吃掉一条没有吃过任何蛇的蛇。并且,我们的最大蛇的实力在不断的变弱。因此我们可以维护两个队列 Q 1 , Q 2 Q1,Q2 Q1,Q2 Q 1 Q1 Q1 存储我们最初的所有蛇,由于题目保证因此它一定是递增的,一旦一条蛇吃掉了最小蛇,就将它放入 Q 2 Q2 Q2 的最前面,显然 Q 2 Q2 Q2 会是单调递增的。

  • 当遇到一条蛇吃完最小蛇后成为最小蛇,那么我们就进入第二种情况。此时我们的最小蛇必须是我们每次吃完了的的最大蛇,而此时我们的 Q 1 , Q 2 Q1,Q2 Q1,Q2 仍然单调。我们考虑直接不把新的最小蛇放进队列,我们直接每次取出 Q 1 , Q 2 Q1,Q2 Q1,Q2 中的最大蛇即可。

    复杂度: O ( T n ) O(Tn) O(Tn)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
using namespace std;

#define LL unsigned long long
#define DB double
#define Pr pair<int,int>
#define X first
#define Y second
#define MAXN 1000000
#define INF 1000000001
#define MOD 1000000007

LL read(){
	LL x=0,F=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')F=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*F;
}

int T,a[MAXN+5],n,k,ans,cnt;
deque<Pr> Q1,Q2;

void solve(){
	cnt=-1;
	while(!Q1.empty())Q1.pop_back();
	while(!Q2.empty())Q2.pop_back();
	for(int i=1;i<=n;i++)Q1.push_back(Pr(a[i],i));
	while(1){
		if(Q1.size()+Q2.size()==2){ans=1;break;}
		int mn=Q1.front().X,mx,id;Q1.pop_front();
		if(Q2.empty()||!Q1.empty()&&Q1.back()>Q2.back())
			mx=Q1.back().X,id=Q1.back().Y,Q1.pop_back();
		else mx=Q2.back().X,id=Q2.back().Y,Q2.pop_back();
		Pr nx=Pr(mx-mn,id);
		if(Q1.empty()||Q1.front()>nx){
			ans=Q1.size()+Q2.size()+2,cnt=0;
			while(1){
				cnt++;
				if(Q1.size()+Q2.size()==1)break;
				if(Q2.empty()||!Q1.empty()&&Q1.back()>Q2.back())
				mx=Q1.back().X,id=Q1.back().Y,Q1.pop_back();
				else mx=Q2.back().X,id=Q2.back().Y,Q2.pop_back();
				nx=Pr(mx-nx.X,id);
				if(!((Q1.empty()||nx<Q1.front())&&(Q2.empty()||nx<Q2.front())))break;
			}break;
		}else Q2.push_front(nx);
	}
	printf("%d\n",ans-(cnt%2==0));
}

int main(){
	freopen("snakes.in","r",stdin);
	freopen("snakes.out","w",stdout);
	T=read()-1,n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	solve();
	while(T--){
		k=read();
		for(int i=1;i<=k;i++){
			int u=read(),v=read();
			a[u]=v;
		}
		solve();
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值