NOIp2023赛后补题报告(分数及题解待补)

日期:2023 年 11月 26日
学号:S09532
姓名:谢松睿
1. 比赛概况:
比赛总分共 4 题,满分 400,赛时拿到 100 分,其中第一题 80分,第二题
20 分,第三题 0 分,第四题 0 分。
2. 比赛过程:
T1比较简单,半个小时就写完了,调了调发现最后两个点会超时,满分代码写了一个多小时,不确定对不对,过了大样例,就转到第二题了,还剩3小时不到,T2暴力写了40分左右之后先暂停,到T3写了5分,T4暴力想写8分没调出来,放弃了,最后40分钟编了几个样例,找了些小bug,优化了一下.
3. 题解报告:
(1) 第一题:dict字典
情况:赛中 80 分,补题困难,未完成
题意:
赛时本题做题想法:
分段
①n=1
②n,m<=300
排序出最大以及最小序列,分别比较
③n,m<=3000
玄学的桶计数,没有清晰的思路,不知道对不对
题解:只要判断每两个字符串最大与最小的字符的数量关系即可判断是否能排除目标序列,分别枚举每一个字符串与其余字符串比较即可得出答案.代码如下
赛时代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,t[3005][35],tm[3005][35],cnt[3005][105];
char tmp[3005];
string s[3005],smin[3005],smax[3005];
bool cmp(char a,char b){
	return a>b;
}
int main(){
	freopen("dict.in","r",stdin);
	freopen("dict.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%s",tmp);
		s[i]=tmp;
		smin[i]=s[i];
		smax[i]=s[i];
	}
	if(n==1){
		printf("1");
	}
	else if(m==1){
		for(int i=1;i<=n;i++){
			int fl=0;
			for(int j=1;j<=n;j++){
				if(j==i){
					continue;
				}
				if(s[j]<s[i]){
					fl=1;
					break;
				}
			}
			if(fl==1){
				printf("0");
			}
			else{
				printf("1");
			}
		}
	}
	else if(n<=300&&m<=300){
		for(int i=1;i<=n;i++){
			sort(smin[i].begin(),smin[i].end());
			sort(smax[i].begin(),smax[i].end(),cmp);
		}
		for(int i=1;i<=n;i++){
			int fl=0;
			for(int j=1;j<=n;j++){
				if(j==i){
					continue;
				}
				if(smax[j]<smin[i]){
					fl=1;
					break;
				}
			}
			if(fl==1){
				printf("0");
			}
			else{
				printf("1");
			}
		}
	}
	else{
		for(int i=1;i<=n;i++){
			for(int j=0;j<m;j++){
				t[i][s[i][j]]++;
			}
		}
		for(int i=1;i<=n;i++){
			for(int k='z';k>='a';k--){
				cnt[i][k]=cnt[i][k+1]+t[i][k];
			}
		}
		for(int i=1;i<=n;i++){
			int fl=0,ml=0,ka=0,kb=0,num=0;
			for(int j=1;j<=n;j++){
				if(i==j){
					continue;
				}
				ml=0;
				ka=0;
				kb=0;
				for(int k='a';k<='z';k++){
					if(cnt[j][k]>=t[i][k]&&t[i][k]!=0){
						ml=1;
						ka=t[i][k];
						kb=t[j][k];
						break;
					}
				}
				if(ml==0){
					fl=1;
					break;
				}
			}
			if(fl==1){
				printf("0");
			}
			else{
				printf("1");
			}
		}
	}
	fclose(stdin);
	fclose(stdout);
	return 0;
}

AC代码
#include<bits/stdc++.h>
using namespace std;
int n,m,t[3005][35],tm[3005][35],cnt[3005][105];
char tmp[3005];
int dmin[3005],dmax[3005];
string s[3005],smin[3005],smax[3005];
bool cmp(char a,char b){
	return a>b;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%s",tmp);
		s[i]=tmp;
		smin[i]=s[i];
		smax[i]=s[i];
	}
	if(n==1){
		printf("1");
	}
	else if(m==1){
		for(int i=1;i<=n;i++){
			int fl=0;
			for(int j=1;j<=n;j++){
				if(j==i){
					continue;
				}
				if(s[j]<s[i]){
					fl=1;
					break;
				}
			}
			if(fl==1){
				printf("0");
			}
			else{
				printf("1");
			}
		}
	}
	else if(n<=300&&m<=300){
		for(int i=1;i<=n;i++){
			sort(smin[i].begin(),smin[i].end());
			sort(smax[i].begin(),smax[i].end(),cmp);
		}
		for(int i=1;i<=n;i++){
			int fl=0;
			for(int j=1;j<=n;j++){
				if(j==i){
					continue;
				}
				if(smax[j]<smin[i]){
					fl=1;
					break;
				}
			}
			if(fl==1){
				printf("0");
			}
			else{
				printf("1");
			}
		}
	}
	else{
	    for(int i=1;i<=n;i++){
			sort(smin[i].begin(),smin[i].end());
			sort(smax[i].begin(),smax[i].end(),cmp);
		}
		for(int i=1;i<=n;i++){
			int fl=0;
			for(int j=1;j<=n;j++){
				if(j==i){
					continue;
				}
				if(smax[j][0]<=smin[i][0]){
					fl=1;
					break;
				}
			}
			if(fl==1){
				printf("0");
			}
			else{
				printf("1");
			}
		}
	}
	return 0;
}

(2) 第二题:tribool
情况:赛中 XX 分,补题困难,未完成
题意:
赛时本题做题想法:
分段
①n,m<=10
深搜,枚举出每一种开局,模拟之后判断与初始序列是否一致,计入答案
②只有TFU
直接模拟,计算开局的U的数量
题解:可以用联通块的思想来做.对于每一种两个元素之间的关系建图,之后判定联通块.
由于题目已经说明每种情况都有解,所以有三种情况.
①联通块中有U,则其余全是U
②联通块中有T或F,其余一定全是T,F
③联通块中无明确赋值,则通过二分图染色判断是否合法,合法则一定全是T,F,否则全是U
赛时代码:
#include<bits/stdc++.h>
using namespace std;
int c,t,n,x,m,a[100005],ans,tmp[100005];
int pre[100005];
struct node{
	int tp,xi,xj;
}p[100005];
char v;
void dfs(int k){
	if(k==n){
		for(int i=1;i<=n;i++){
			tmp[i]=pre[i];
		}
		for(int i=1;i<=m;i++){
			if(p[i].tp==1){
				pre[p[i].xi]=1;
			}
			if(p[i].tp==2){
				pre[p[i].xi]=2;
			}
			if(p[i].tp==3){
				pre[p[i].xi]=3;
			}
			if(p[i].tp==4){
				pre[p[i].xi]=pre[p[i].xj];
			}
			if(p[i].tp==5){
				if(pre[p[i].xj]==1)
					pre[p[i].xi]=2;
				else if(pre[p[i].xj]==2)
					pre[p[i].xi]=1;
				else if(pre[p[i].xj]==3)
					pre[p[i].xi]=3;
			}
		}
		int fl=0,cnt=0;
		for(int i=1;i<=n;i++){
			if(tmp[i]!=pre[i]){
				fl=1;
				break;
			}
			if(pre[i]==3){
				cnt++;
			}
		}
		if(fl==0){
			ans=min(ans,cnt);
		}
		for(int i=1;i<=n;i++){
			pre[i]=tmp[i];
		}
		return;
	}
	for(int i=1;i<=3;i++){
		pre[k+1]=i;
		dfs(k+1);
	}
}
int main(){
	freopen("tribool.in","r",stdin);
	freopen("tribool.out","w",stdout);
	scanf("%d%d",&c,&t);
	if(c==1||c==2){
	while(t--){
		ans=0x3f3f3f3f;
		scanf("%d%d",&n,&m);
		int l,r;
		for(int i=1;i<=m;i++){
			scanf("%s",&v);
			if(v=='T'){
				scanf("%d",&p[i].xi);
				p[i].tp=1;
			}
			if(v=='F'){
				scanf("%d",&p[i].xi);
				p[i].tp=2;
			}
			if(v=='U'){
				scanf("%d",&p[i].xi);
				p[i].tp=3;
			}
			if(v=='+'){
				scanf("%d%d",&p[i].xi,&p[i].xj);
				p[i].tp=4;
			}
			if(v=='-'){
				scanf("%d%d",&p[i].xi,&p[i].xj);
				p[i].tp=5;
			}
		}
		dfs(0);
		printf("%d\n",ans);
	}
	}
	else if(c==3||c==4){
	while(t--){
		ans=0;
		scanf("%d%d",&n,&m);
		for(int i=1;i<=m;i++){
			scanf("%s",&v);
			if(v=='T'){
				scanf("%d",&x);
				pre[x]=1;
			}
			if(v=='F'){
				scanf("%d",&x);
				pre[x]=2;
			}
			if(v=='U'){
				scanf("%d",&x);
				pre[x]=3;
			}
		}
		for(int i=1;i<=n;i++){
			//cout<<pre[i];
			if(pre[i]==3){
				ans++;
			}
		}
		printf("%d\n",ans);
	}	
	}
	fclose(stdin);
	fclose(stdout);
	return 0;
}
AC代码
(3) 第三题:expand
情况:赛中 0 分,补题困难,完成部分
题意:
赛时本题做题想法:
只考虑了1的情况,只要新的序列不全都是相同的源序列中的那个数就行
题解:
对于所给的两个序列,我们可以这样考虑↓
只要经过能选的点,能从序列头走到序列尾部即可
为减少时间复杂度,可以做以下优化:如果有纵横的线(即横向最小值以及纵向最大值)则只要能从起点和终点走到交点即可
如果有多个min,max
同理
赛时代码:
#include<bits/stdc++.h>
using namespace std;
int c,n,m,q,x[500005],y[500005],tx[500005],ty[500005];
int main(){
	freopen("expand.in","r",stdin);
	freopen("expand.out","w",stdout);
	scanf("%d%d%d%d",&c,&n,&m,&q);
	for(int i=1;i<=n;i++){
		scanf("%d",&x[i]);
	}
	for(int i=1;i<=m;i++){
		scanf("%d",&y[i]);
	}
	if(c==1||c==2){
		printf("1");
		int kx,ky,p,v;
		for(int i=1;i<=n;i++){
			tx[i]=x[i];
		}
		for(int i=1;i<=m;i++){
			ty[i]=y[i];
		}		
		while(q--){
			scanf("%d%d",&kx,&ky);
			for(int i=1;i<=kx;i++){
				scanf("%d%d",&p,&v);
				tx[p]=v;
			}
			for(int i=1;i<=ky;i++){
				scanf("%d%d",&p,&v);
				ty[p]=v;
			}
			if(c==1){
				if(tx[1]!=ty[1]){
					printf("1");
				}
				else{
					printf("0");
				}
			}
			else{
				printf("%d",(q*13+6)%2);
			}
		}
	}
	fclose(stdin);
	fclose(stdout);
	return 0;
}

AC代码
(4) 第四题:run
情况:赛中 0分,补题困难,未完成
题意:
赛时本题做题想法:
写暴力,没调出来
题解:
线段树+dp
状态:
f[i]:第i天为止,且第i天跑了
枚举跑的起点j[max(1,i-k+1),i],f[i]=i到j的所有吃饭-(i-j+1)*d+f[1]->f[j-2]中的最大值与0比较的最大值
答案:ans=max(0,f[1],f[2]...f[n])
优化
设g[j](j为起点,到f[i]的最大值)=前总分+f[j]
f[i]=max(g[j])
赛时代码:无
AC代码
4. 赛后总结:
本次比赛出现了时间分配不合理的问题,第一题花费时间太长,以后需要学会合理舍弃一部分分,可能有更多机会.
  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值