2022年寒假训练赛(2020级)题解

出题人

ABD: 晋云涛
C: 袁红婷
EF:谢承洋
GH:温世民
I:徐垚
验题人:晋云涛
题解撰写人:晋云涛,此题解是我把所有的题目都做了一遍再来写的,由于水平有限,题解写得有问题的地方可以来找我,或者直接问出题人。

A《关于508的门老是被反锁这件事》

思路

bfs+优化,这道题测试组数很多,直接爆搜会超时的,所以我们需要考虑优化。
需要想清楚这一点:
先通过随意的旋转方法把S变为0000,然后用同样的方法,把T变为T2。将S变为T的过程,等价于将0000变为T2的过程,两者的最少开锁次数是一样的。
所以我们可以预处理出0000到所有其他状态的最少次数,这样即可通过

AC代码

#include<bits/stdc++.h>

using namespace std;
const int N = 1e5+9;
unordered_map<string,int> st; 
unordered_map<string,int> ans; 
struct node
{
	string s;
	int step;
};
char s[10],t[10];
void bfs(){
	queue<node> q;
	q.push({"0000",0});
	st["0000"]=1;
	ans["0000"]=0;
	while(q.size()){
		auto tmp = q.front();
		q.pop();
		//枚举一次操作几位 
		for(int i=1; i<=4; ++i){
			//从哪里开始 
			for(int j=1; j<=4-i+1; j++){
				int l=j-1,r=j+i-2;
			//	cout<<l<<" "<<r<<endl;
				string s2=tmp.s;
				for(int k=l; k<=r; ++k) {					
					int x=(tmp.s[k]-'0'+1+10)%10;
					s2[k]=x+'0';
				}
				//cout<<"s2 "<<s2<<endl;
				if(!st[s2]) q.push({s2,tmp.step+1}) , st[s2]=1,ans[s2]=tmp.step+1;
				s2=tmp.s;
				for(int k=l; k<=r; ++k) {
					int x=(tmp.s[k]-'0'-1+10)%10;
					s2[k]=x+'0';
				}
				//	cout<<"s2 "<<s2<<endl;
				if(!st[s2]) q.push({s2,tmp.step+1}) , st[s2]=1,ans[s2]=tmp.step+1;;
			} 
		}
		//	exit(0);
	}
}
int main()
{
	bfs(); 
	//cout<<ans["1111"]<<endl;
	int n;
	cin>>n; 
	while(n--)
	{		
		scanf("%s %s",s+1,t+1);
		int num=0;
		string S="",T="";
		for(int i=1; i<=4; ++i){
			int x=s[i]-'0',y=t[i]-'0';
			y=(y-x+10)%10;
			t[i]=y+'0';
			T+=t[i];
		}
		//cout<<T<<endl;
		printf("%d\n",ans[T]);
	}
}

B 昆虫关系

这道题可以用带权并查集,种类并查集,二分图染色三种方法处理

思路1:带权并查集

如果做过食物链那道经典并查集题目的并且理解了的同学,做这道题简直so easy,这道题也是经典带权并查集板子题。
我们用0表示同性,1表示异性,跑带权并查集板子即可,注意取模

AC代码

#include<iostream>
#include<cstdio>
using namespace std;
const int N = 1e6+9;
int p[N*2];
int val[N]; 
int find(int x){
	if(x!=p[x]) {
		int t=p[x];
		p[x]=find(p[x]);
		val[x]=(val[x]+val[t])%2;
	}
	return p[x];
}
void merge(int a,int b,int c){
	int fa=find(a),fb=find(b);
	if(fa!=fb){
		p[fa]=fb;
		val[fa]=(val[b]+c-val[a]+2)%2;
	}
}
int main(){
	
	int t,ca=0;
	cin>>t;
	while(t--){
		int n,m;
		scanf("%d %d",&n,&m);
		for(int i=1; i<=n*2; ++i) p[i]=i,val[i]=0;
		bool ok=false;
		for(int i=0; i<m; ++i){
			int a,b;
			scanf("%d %d",&a,&b);
			int fa=find(a),fb=find(b);
			if(fa==fb){
				if((val[a]-val[b]+2)%2==0) ok=true;
			}
			else{
				merge(a,b,1);
			}
		}
		printf("Scenario #%d:\n",++ca);
		if(ok) printf("Suspicious bugs found!");
		else printf("No suspicious bugs found!");
		if(t) printf("\n\n"); 
	}
	
	return 0;
} 

思路2:种类并查集

[ 1 , n ] [1,n] [1,n] 表示同性, [ n + 1 , 2 ∗ n ] [n+1,2*n] [n+1,2n]表示异性,跑种类并查集板子

AC代码2

#include<iostream>
#include<cstdio>
using namespace std;
const int N = 1e6+9;
int p[N*2];
int find(int x){
	if(x!=p[x]) p[x]=find(p[x]);
	return p[x];
}
void merge(int a,int b){
	int fa=find(a),fb=find(b);
	if(fa!=fb) p[fa]=fb;
}
int main(){
	
	int t,ca=0;
	cin>>t;
	while(t--){
		int n,m;
		scanf("%d %d",&n,&m);
		for(int i=1; i<=n*2; ++i) p[i]=i;
		bool ok=false;
		for(int i=0; i<m; ++i){
			int a,b;
			scanf("%d %d",&a,&b);
			int fa=find(a),fb=find(b);
			if(fa==fb) ok=true;
			else{
				merge(a,b+n);
				merge(b,a+n);
			}
		}
		printf("Scenario #%d:\n",++ca);
		if(ok) printf("Suspicious bugs found!");
		else printf("No suspicious bugs found!");
		if(t) printf("\n\n"); 
	}
	
	return 0;
} 

思路3:二分图染色

  • a与b之间是异性,等价于a与b要被染成不同的颜色,出现矛盾等价于存在奇环,即不是二分图。比如1和2是异性,2和3是异性,3和1是异性,这里出现了奇环,所以不是二分图,所以有矛盾。
  • 所以可以用染色法判定是不是二分图,从而等价判定是否有矛盾。

AC代码3

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 1e6+9;
int e[N*2],ne[N*2],h[N*2],idx;
int col[N];
void add(int a,int b){
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
bool dfs(int p,int c){
	col[p]=c;
	for(int i=h[p]; i!=-1; i=ne[i]){
		int j=e[i];
		if(!col[j]){
			if(!dfs(j,3-c)) return false;
		}
		else if(col[j]!=3-c) return false;
	}
	return true;
}
int main(){
	
	int t,ca=0;
	cin>>t;
	while(t--){
		int n,m;
		memset(h,-1,sizeof h);
		memset(col,0,sizeof col);
		idx=0;
		scanf("%d %d",&n,&m);
		bool ok=true;
		for(int i=0; i<m; ++i){
			int a,b;
			scanf("%d %d",&a,&b);
			add(a,b),add(b,a);
		}
		for(int i=1; i<=n; i++){
			if(!col[i]){
				ok=dfs(i,1);
				if(!ok) break;
			}
		}
		printf("Scenario #%d:\n",++ca);
		if(!ok) printf("Suspicious bugs found!");
		else printf("No suspicious bugs found!");
		if(t) printf("\n\n"); 
	}
	
	return 0;
} 

C 谁输了谁请客干饭

思路

  • 本场签到题1,简单博弈,因为先手第一次最多可以拿n-1个糖果,以后的每一次,每个人都只能拿不超过之前的人拿的个数的奇数个
  • 所以先手第一次可以拿 n − 2 n-2 n2 个( n > 2 n>2 n>2 ),那么后手只能拿1个,最后剩下一个先手拿,先手胜利。
  • 如果 n = 2 n=2 n=2 ,显然后手胜利

AC代码

#include<bits/stdc++.h>

using namespace std;

int main(){
	
	int t;
	cin>>t;
	while(t--){
		int n;
		scanf("%d",&n);
		if(n>2) puts("qxj_qing_ke_gan_fan");
		else puts("yxz_qing_ke_gan_fan");
	}
	return 0;
} 

D 新年快乐!

思路

本场签到题2,一张图胜过千言万语:
在这里插入图片描述
题目叫求第 K K K小的数其实就是个二进制转换,把1换成3就行了

AC代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rep(i,x,y) for(int i=x; i<=y; i++)
#define per(i,x,y) for(int i=x; i>=y; i--) 
const int N = 1e5+9;
int ans[N];
int main()
{
	ll n;
	cin>>n;
	int idx=0;
	while(n){
		ans[idx++]=n%2;
		n/=2;
	}
	per(i,idx-1,0) printf("%d",ans[i]?3:0);
	return 0;
 } 

E 恐怖之夜:召唤仪式

思路

  • 题意应该很明确,就是对于每一个 a i a_i ai,求出数组中其他能整除它的数的个数。直接暴力循环必定超时。
  • 因为 a i ≤ 1 0 6 a_i \leq 10^6 ai106, 我们从1 枚举到1e6, 对于每一个 i i i, 我们处理出它倍数即可,具体看代码
  • 时间复杂度:对于每一个 i i i ,枚举它的倍数,需要枚举 n i \frac{n}{i} in 次 ,所有的数需要枚举 n ∗ ∑ i = 1 n 1 i n* \sum^n_{i=1} \frac{1}{i} ni=1ni1 次, 调和级数 ∑ i = 1 n 1 i \sum^n_{i=1} \frac{1}{i} i=1ni1 的时间复杂度是 l o g ( n ) log(n) log(n 级别的。所以时间复杂度为 n l o g ( n ) nlog(n) nlog(n),完美通过

AC代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,a[N],cnt[N],ans[N];

int main() {
    int n;
    cin>>n;
    for(int i=1; i<=n; i++) scanf("%d",a+i),cnt[a[i]]++;
    for(int i=1; i<=1e6; i++){
    	for(int j=i; j<=1e6; j+=i){
    		ans[j]+=cnt[i];
		}
	}
	for(int i=1; i<=n; i++) printf("%d\n",ans[a[i]]-1);
    
    return 0;
}

F 恐怖之夜:圣歌圣咏

思路

组合数学

  • 首先从 m m m 个数里面选 n − 1 n-1 n1 个不同的数,选法有 C m n − 1 C_m^{n-1} Cmn1
  • 排除2个身高相同的人,选一个最高的人,有 n − 2 n-2 n2种方法

G 圆桌会议

思路

基环树,拓扑排序,个人觉得有个大佬讲的十分好,于是就直接放他的博客了。
大佬博客

AC代码

#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=x; i<=y; i++)
#define per(i,x,y) for(int i=x; i>=y; i--)
using namespace std;
const int N = 1e5+9;
int g[N];//存图
int d[N];//d[i]代表点i的入度为d[i] 
vector<int> rg[N];//正向图
queue<int> q; 
bool vis[N]; 
int n;
//拓扑排序删除树枝 
void topsort() {
	rep(i,0,n-1) {
		if(d[i]==0) q.push(i);
	}
	while(q.size()){
		int p = q.front();
		q.pop();
		int np = g[p];
		d[np]--;
		if(d[np]==0) q.push(np);
	}
}
int dfs(int u){
	//cout<<u<<endl;
	int maxdep=1;
	if(rg[u].size()==0) return 1;
	rep(i,0,rg[u].size()-1){
		int nu = rg[u][i];
		if(!d[nu]) maxdep=max(maxdep,dfs(nu)+1); 
	}
	return maxdep;
}
int main() {
	cin>>n;
	//输入存图,g[i]表示点 i喜欢和g[i]坐在一起
	//也就是点i到g[i]有一条边
	rep(i,0,n-1) {
		scanf("%d",&g[i]);
		d[g[i]]++;//入度 
		rg[g[i]].push_back(i);//反图 
	}
	//拓扑排序删除边 
	topsort();
	//接下来去找基环
	int ans1=0,ans2=0;
	rep(i,0,n-1){
		if(d[i]<=0) continue;
		//拓扑删边后基环上的点,入度肯定为1
		int cnt=1;//统计环上点的个数
		d[i]=-1;
		for(int j=g[i]; j!=i; j=g[j]){
			d[j]=-1;
			cnt++;
		}
	//	cout<<cnt<<'\n';
		//如果基环大小为2,那么找最长的链 
		if(cnt==2) ans1+=dfs(i)+dfs(g[i]);
		else ans2=max(ans2,cnt); 
		//cout<<ans1<<'\n';
	} 
	cout<<max(ans1,ans2)<<'\n';
	return 0;
}

H 消灭

思路

签到,排个序处理就行

AC代码

#include <bits/stdc++.h>
#define ll long long
#define rep(i,x,y) for(int i=x; i<=y; i++)
using namespace std;
const int N = 1e5+9;
int b[N];
int main()
{
     int n,m;
     cin>>n>>m;
     for(int i=1; i<=n; i++) scanf("%d",b+i);
     sort(b+1,b+n+1);
     ll sum=m;
     bool ok=1;
     rep(i,1,n){
     	if(sum<b[i]) {
     		ok=0;
     		break;
     	}
     	sum+=b[i];
	 }
	 printf("%s",ok?"yes":"no");
    return 0;
}

I Fibonacci

思路

做为数学弱鸡的我铭记"数学上来先打表"的口诀,此题也是打表找规律,很好看出,算个签到题吧

打表程序

#include <bits/stdc++.h>
#define ll long long
#define rep(i,x,y) for(int i=x; i<=y; i++)
using namespace std;
const int N = 1e5+9;
ll f[N];
int main()
{
	f[0]=7,f[1]=11;
	for(int i=2; i<=50; i++){
		f[i]=f[i-1] + f[i-2];
		cout<<f[i]<<" ";
		if(f[i]%3==0) puts("1");
		else puts("0");
	}
    return 0;
}

1表示是3的倍数,0表示不是
在这里插入图片描述

AC代码

#include <bits/stdc++.h>
#define ll long long
#define rep(i,x,y) for(int i=x; i<=y; i++)
using namespace std;
const int N = 1e5+9;
ll f[N];
int main()
{

	int n;
	while(cin>>n){
		if(n<2) puts("no");
		else {
			if((n-2)%4==0) puts("yes");
			else puts("no");
		}
	}
    return 0;
}

posted @ 2022-08-28 08:43  翔村亲亲鸟  阅读(76)  评论(0编辑  收藏  举报