CSP 2022 提高组&普及组总结

本文解析了提高组与普及组的编程竞赛题目,包括假期计划、策略游戏、乘方、解密等多个问题,提供了详细的解题思路与代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

提高组

T1 假期计划

题目大意:有nnn个点,mmm条边,每个点都有一个权值。现在要从第一个点开始,走到四个点在走回家,每走一次可以经过kkk个点。

f[i][0/1/2]f[i][0/1/2]f[i][0/1/2]表示从第一个点走两次到第iii个点的权值和的最大值、次大值、次次大值,用bfsbfsbfs求出fff值,最后求解即可。

code

#include<bits/stdc++.h>
using namespace std;
int n,m,k,x,y,tot=0,d[20005],l[20005],r[20005],z[3005],g[3005][3],v[2505][2505];
long long ans,a[3005];
queue<int>q;
void add(int xx,int yy){
	l[++tot]=r[xx];d[tot]=yy;r[xx]=tot;
}
void bfs(int v0){
	q.push(v0);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=r[u];i;i=l[i]){
			if(z[d[i]]) continue;
			z[d[i]]=1;v[v0][d[i]]=v[v0][u]+1;
			q.push(d[i]);
		}
	}
}
int main()
{
//	freopen("holiday.in","r",stdin);
//	freopen("holiday.out","w",stdout);
	scanf("%d%d%d",&n,&m,&k);++k;
	for(int i=2;i<=n;i++) scanf("%lld",&a[i]);
	a[0]=a[1]=-2e18;
	for(int i=1;i<=m;i++){
		scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++) v[i][j]=k+1;
		v[i][i]=0;
		memset(z,0,sizeof(z));
		z[i]=1;
		bfs(i);
		v[i][i]=v[i][1]=k+1;
	}
	for(int i=2;i<=n;i++){
		if(v[1][i]>k) continue;
		for(int j=2;j<=n;j++){
			if(v[i][j]>k) continue;	
			if(a[i]>a[g[j][0]]){
				g[j][2]=g[j][1];g[j][1]=g[j][0];g[j][0]=i;
			}
			else if(a[i]>a[g[j][1]]){
				g[j][2]=g[j][1];g[j][1]=i;
			}
			else if(a[i]>a[g[j][2]]){
				g[j][2]=i;
			}
		}
	}
	for(int i=2;i<=n;i++){
		for(int j=2;j<=n;j++){
			if(v[i][j]>k) continue;
			for(int v1=0;v1<=2;v1++){
				for(int v2=0;v2<=2;v2++){
					if(i==g[j][v2]||g[i][v1]==j||g[i][v1]==g[j][v2]) continue;
					ans=max(ans,a[i]+a[j]+a[g[i][v1]]+a[g[j][v2]]);
				}
			}
		}
	}
	printf("%lld",ans);
	fclose(stdin);
	fclose(stdout);
	return 0;
}

T2 策略游戏

题目大意:给一个长度为nnn的数组AAA和一个长度为mmm的数组BBBCi,j=Ai×BjC_{i,j}=A_i\times B_{j}Ci,j=Ai×Bj。有qqq轮游戏,每轮给定一个范围,小L先选一个iii,小QQQ后选一个jjj。小LLL想使Ci,jC_{i,j}Ci,j尽可能大,小Q想使Ci,jC_{i,j}Ci,j尽可能小。两人都会使用最优策略,求每轮游戏中最后的Ci,jC_{i,j}Ci,j的值

分类讨论:
1.若在范围中选择的AiA_iAi大于000,则BiB_iBi为范围中的最小值

  • BiB_iBi最小值大于000,则AiA_iAi取大于000的最大值
  • BiB_iBi最小值小于000,则AiA_iAi取大于000的最小值
  • BiB_iBi最小值等于000,则AiA_iAi取大于000的任意值

2.若在范围中选择的AiA_iAi小于000,则BiB_iBi为范围中的最大值

  • BiB_iBi最大值大于000,则AiA_iAi取小于000的最大值
  • BiB_iBi最大值小于000,则AiA_iAi取小于000的最小值
  • BiB_iBi最大值等于000,则AiA_iAi取小于000的任意值

3.若在范围中选择的AiA_iAi等于000,则Ci,jC_{i,j}Ci,j000

在三种情况中取最小值就是答案

我用的是倍增,用线段树也可以过,时间复杂度为O(qlog⁡n)O(q\log n)O(qlogn)

code

#include<bits/stdc++.h>
using namespace std;
int n,m,q,l1,r1,l2,r2,lg[100005],ze[100005];
long long mx,mn,ans,a[100005],b[100005],mxb[100005][20],mnb[100005][20];
long long mx1[100005][20],mx2[100005][20],mn1[100005][20],mn2[100005][20];
long long fmx1(int l,int r){
	if(l==r) return mx1[l][0];
	return max(mx1[l][lg[r-l+1]],mx1[r-(1<<lg[r-l+1])+1][lg[r-l+1]]);
}
long long fmn1(int l,int r){
	if(l==r) return mn1[l][0];
	return min(mn1[l][lg[r-l+1]],mn1[r-(1<<lg[r-l+1])+1][lg[r-l+1]]);
}
long long fmx2(int l,int r){
	if(l==r) return mx2[l][0];
	return max(mx2[l][lg[r-l+1]],mx2[r-(1<<lg[r-l+1])+1][lg[r-l+1]]);
}
long long fmn2(int l,int r){
	if(l==r) return mn2[l][0];
	return min(mn2[l][lg[r-l+1]],mn2[r-(1<<lg[r-l+1])+1][lg[r-l+1]]);
}
int main()
{
//	freopen("game.in","r",stdin);
//	freopen("game.out","w",stdout);
	scanf("%d%d%d",&n,&m,&q);lg[0]=-1;
	for(int i=1;i<=max(n,m);i++) lg[i]=lg[i/2]+1;
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
		if(a[i]>0){
			mx1[i][0]=mn1[i][0]=a[i];
			mx2[i][0]=-1e9-1;mn2[i][0]=0;
		}
		else if(a[i]<0){
			mx2[i][0]=mn2[i][0]=a[i];
			mx1[i][0]=0;mn1[i][0]=1e9+1;
		}
		ze[i]=ze[i-1]+(a[i]==0);
	}
	for(int i=1;i<=m;i++){
		scanf("%lld",&b[i]);
		mxb[i][0]=mnb[i][0]=b[i];
	}
	for(int i=1;i<=19;i++){
		for(int j=1;j+(1<<i)-1<=n;j++){
			mx1[j][i]=max(mx1[j][i-1],mx1[j+(1<<i-1)][i-1]);
			mx2[j][i]=max(mx2[j][i-1],mx2[j+(1<<i-1)][i-1]);
			mn1[j][i]=min(mn1[j][i-1],mn1[j+(1<<i-1)][i-1]);
			mn2[j][i]=min(mn2[j][i-1],mn2[j+(1<<i-1)][i-1]);
		}
		for(int j=1;j+(1<<i)-1<=m;j++){
			mxb[j][i]=max(mxb[j][i-1],mxb[j+(1<<i-1)][i-1]);
			mnb[j][i]=min(mnb[j][i-1],mnb[j+(1<<i-1)][i-1]);
		}
	}
	while(q--){
		ans=-2e18;
		scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
		if(l2==r2) mx=mn=b[l2];
		else{
			mx=max(mxb[l2][lg[r2-l2+1]],mxb[r2-(1<<lg[r2-l2+1])+1][lg[r2-l2+1]]);
			mn=min(mnb[l2][lg[r2-l2+1]],mnb[r2-(1<<lg[r2-l2+1])+1][lg[r2-l2+1]]);
		}
		if(fmx1(l1,r1)>0){
			if(mn<0) ans=max(ans,mn*fmn1(l1,r1));
			else ans=max(ans,mn*fmx1(l1,r1));
		}
		if(fmn2(l1,r1)<0){
			if(mx<0) ans=max(ans,mx*fmn2(l1,r1));
			else ans=max(ans,mx*fmx2(l1,r1));
		}
		if(ze[r1]-ze[l1-1]>0) ans=max(ans,0ll);
		printf("%lld\n",ans);
	}
	fclose(stdin);
	fclose(stdout);
	return 0;
}

T3 星战

没有AC

T4 数据传输

没有AC


提高组总结

没有拿完应得的分,分数230。


普及组

T1 乘方

题目大意:给出aaabbb,如果aba^bab小于等于10910^9109,则输出aba^bab,否则输出−1-11

快速幂,每做一次判断一次,时间复杂度为O(log⁡n)O(\log n)O(logn)

直接乘也行,特判111之后,因为230>1092^{30}>10^9230>109,所以最多只会乘不超过303030次。

我用的是快速幂。

code

#include<bits/stdc++.h>
using namespace std;
long long a,b,x,s,inf=1e9;
int main()
{
// 	freopen("pow.in","r",stdin);
// 	freopen("pow.out","w",stdout);
	scanf("%lld%lld",&a,&b);
	x=a;s=1;
	for(;b;b>>=1){
		if(x>inf||s>inf){
			printf("-1");
			return 0;
		}
		if(b&1) s=s*x;
		x=x*x;
	}
	if(s>inf) s=-1; 
	printf("%lld",s);
	fclose(stdin);
	fclose(stdout);
	return 0;
}

T2 解密

题目大意:kkk次询问,每次给出n,e,dn,e,dn,e,d,求正整数p,qp,qp,q,使n=p×q,e×d=(p−1)(q−1)+1n=p\times q,e\times d=(p-1)(q-1)+1n=p×qe×d=(p1)(q1)+1

p×q=np\times q=np×q=n
p+q=p×q−(p×q−p−q+2)+2=n−e×d+2p+q=p\times q-(p\times q-p-q+2)+2=n-e\times d+2p+q=p×q(p×qpq+2)+2=ne×d+2

p×q,p+qp\times q,p+qp×q,p+q已知,那么p,qp,qp,q就是方程x2−(p+q)x+p×q=0x^2-(p+q)x+p\times q=0x2(p+q)x+p×q=0的两个根,用求根公式即可求出,最后判断是否为正整数即可。

求根公式:x1=−b+b2−4ac2a,x2=−b−b2−4ac2ax_1=\dfrac{-b+\sqrt{b^2-4ac}}{2a},x_2=\dfrac{-b-\sqrt{b^2-4ac}}{2a}x1=2ab+b24ac,x2=2abb24ac

code

#include<bits/stdc++.h>
using namespace std;
int t;
long long a,b,c,d,d1,v1,v2,p,q;
int main()
{
// 	freopen("decode.in","r",stdin);
// 	freopen("decode.out","w",stdout);
	scanf("%d",&t);
	while(t--){
		scanf("%lld%lld%lld",&a,&b,&c);
		v1=a-b*c+2;
		v2=a;
		d=v1*v1-4*v2;
		if(d<0){
			printf("NO\n");
			continue;
		}
		d1=sqrt(d);
		if(d1*d1!=d){
			printf("NO\n");
			continue;
		}
		d=d1;
		if((v1+d)%2==1){
			printf("NO\n");
			continue;
		}
		p=(v1-d)/2;q=(v1+d)/2;
		if(p<=0){
			printf("NO\n");
			continue;
		}
		printf("%lld %lld\n",p,q);
	}
	fclose(stdin);
	fclose(stdout);
	return 0;
}

T3 逻辑表达式

题目大意:给出一个逻辑表达式,其中包含左右括号,&,|,0,1。
括号优先级最大,&优先级第二,|优先级最小。当计算a&b时,若a为0,则不计算b;当计算a|b时,若a为1,则不计算b。称以上两种情况为短路。求逻辑表达式的值,以及&短路的次数和|短路的次数。如果某处短路包含在更外层的短路,则这处短路不计。

gt(l,r)gt(l,r)gt(l,r)表示求逻辑表达式中[l,r][l,r][l,r]部分&和|分别短路的次数,返回该部分的值。那么在处理[l,r][l,r][l,r]的时候:

  • 如果这一段有|且不被括号包含,则处理|之前的,如果=1,则|短路一次
  • 否则如果这一段有&且不被括号包含,则处理&之前的,如果=0,则&短路一次
  • 如果这一段有|或&但都被括号包含,那l,rl,rl,r的位置一定是左右括号,gt(l+1,r−1)gt(l+1,r-1)gt(l+1,r1)
  • 如果这一段没有|也没有&,那么l==rl==rl==r,这个位置一定是0或1,返回相应值即可

code

#include<bits/stdc++.h>
using namespace std;
int n,v1,v2,ans,h[1000005],v[1000005],dep[1000005];
int vt[1000005],lt[1000005],vk[1000005],lk[1000005];
char s[1000005];
int gt(int l,int r,int d){
	if(l==r) return s[l]-'0';
	if(s[l]=='('&&s[r]==')'&&v[l]==r) return gt(l+1,r-1,d+1);
	if(lt[r]>=l){
		int i=lt[r];
		if(gt(l,i-1,d)){
			++v2;return 1;
		}
		else return gt(i+1,r,d);
	}
	int i=lk[r];
	if(!gt(l,i-1,d)){
		++v1;return 0;
	}
	else return gt(i+1,r,d);
}
int main()
{
	// freopen("expr.in","r",stdin);
	// freopen("expr.out","w",stdout);
	scanf("%s",s+1);
	n=strlen(s+1);
	for(int i=1,k=0;i<=n;i++){
		if(s[i]=='(') h[++k]=i;
		else if(s[i]==')'){
			v[h[k]]=i;--k;
		}
		dep[i]=k;
		if(s[i]=='|') vt[k]=i;
		else if(s[i]=='&') vk[k]=i;
		lt[i]=vt[k];lk[i]=vk[k];
	}
	ans=gt(1,n,0);
	printf("%d\n%d %d",ans,v1,v2);
	fclose(stdin);
	fclose(stdout);
	return 0;
}

T4 上升点列

题目大意:给出平面上的nnn个点和一个kkkkkk表示可以在平面上添加kkk个点。添加后要从这n+kn+kn+k个点中选出若干个整数点并组成一个序列,使序列中任意相邻两点间的欧几里得距离恰好为111且横坐标、纵坐标值均单调不减。

首先,将点按xxx值从小到大排序,xxx值相等则按yyy值从小到大排序。

然后,我们设f[i][j]f[i][j]f[i][j]表示以第iii个点为终点,已经放了jjj个点的最长序列的长度。每两个点连一条边,共连n2n^2n2条边。依次遍历每个点,更新f[i][j]f[i][j]f[i][j]。每个点更新的时间复杂度为O(nk)O(nk)O(nk),总时间复杂度为O(n2k)O(n^2k)O(n2k)

code

#include<bits/stdc++.h>
using namespace std;
int n,k,tot=0,ans=0,d[500005],l[500005],r[500005],v[500005],ct[505],f[505][105];
queue<int>q;
struct node{
	int x,y;
}w[505];
void add(int xx,int yy,int zz){
	l[++tot]=r[xx];d[tot]=yy;r[xx]=tot;v[tot]=zz;++ct[yy];
}
int main()
{
	// freopen("point.in","r",stdin);
	// freopen("point.out","w",stdout);
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++){
		scanf("%d%d",&w[i].x,&w[i].y);
		f[i][0]=1;
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(i!=j&&w[i].x<=w[j].x&&w[i].y<=w[j].y){
				add(i,j,w[j].x-w[i].x+w[j].y-w[i].y-1);
			}
		}
	}
	for(int i=1;i<=n;i++){
		if(!ct[i]) q.push(i);
	}
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=r[u];i;i=l[i]){
			for(int j=v[i];j<=k;j++){
				f[d[i]][j]=max(f[d[i]][j],f[u][j-v[i]]+1);
			}
			--ct[d[i]];
			if(ct[d[i]]==0) q.push(d[i]);
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=0;j<=k;j++){
			ans=max(ans,f[i][j]);
		}
	}
	printf("%d",ans+k);
	fclose(stdin);
	fclose(stdout);
	return 0;
}

普及组总结

普及组考得还不错,分数400分。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值