codeforces 1700分

文章目录

1.D. Solve The Maze

n * m的迷宫
点表示空,#表示墙,G代表good man,B代表bad man

问能否在空地建造墙,使得所有好人可以到达(n,m),所有坏人不能到达
目的地是(n,m),那么倒推,首先得能遇到所有好人,而且不能有好人和坏人相邻,因为相邻的话,坏人可以跟着好人走
在坏人四周都围上墙,在满足上述条件的情况下,坏人四周围上墙对好人没影响

trick:

1.如果终点已知或结果已知,那么倒推

2.以下,ty=j+dy[k]写成ty=i+dy[k],真的很难检查出来,以后要注意这点

for(int k=0;k<4;k++){
int tx=i+dx[k],ty=j+dy[k];				
if(tx<1||tx>n||ty<1||ty>m) continue;
}
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=55;
char s[N][N];
bool vis[N][N];
int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1};
int n,m;
int cnt;
void dfs(int x,int y){
	vis[x][y]=true;
	if(s[x][y]=='G') cnt++;
	for(int i=0;i<4;i++){
		int tx=x+dx[i],ty=y+dy[i];
		if(tx<1||tx>n||ty<1||ty>m||s[tx][ty]=='#'||vis[tx][ty]) continue;
		dfs(tx,ty);
	}
}
void solve() {
	cin>>n>>m;
	memset(vis,false,sizeof vis);
	int sum=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>s[i][j];
			if(s[i][j]=='G') sum++;
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(s[i][j]=='B'){
				for(int k=0;k<4;k++){
					int tx=i+dx[k],ty=j+dy[k];
					if(tx<1||tx>n||ty<1||ty>m) continue;
					if(s[tx][ty]=='G'){
						cout<<"NO"<<endl;
						return;
					}
					if(s[tx][ty]=='.') s[tx][ty]='#';
				}
			}
		}
	}
	if(s[n][m]=='#'&&sum){
		cout<<"NO"<<endl;
		return;
	}
	cnt=0;
	dfs(n,m);
	if(cnt==sum) cout<<"YES"<<endl;
	else cout<<"NO"<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

2.D. Another Problem About Dividing Numbers

Codeforces 1538D. Codeforces Round #725 (Div. 3) D(思维+构造) - AcWing
我们只需要求出令 a = b 的最小操作次数和最大操作次数,如果 k 在这个范围内,则一定有解。

求最大操作次数,贪心的考虑要让 a 和 b 通过尽可能多的操作都变成 1,设 a 的质因子有 c 个,
b 的质因子有 d 个,显然最大操作次数就是 c + d

求最小操作次数,如果 a = b,则最小操作次数为 0,如果 a 和 b 的其中一个等于 a 和 b 的最大公因数,
则最小操作次数为 1,否则 a 和 b 没有任何联系,令他们各自变成 1,最小操作次数为 2

这里还有一种特殊情况,当 k = 1 时,说明必须 a 和 b 中的一个是 a 和 b 的最大公因数,另一个不是,
而最小操作次数是 0 时,意味着 a = b,则我们无法令操作次数为 1,因此需要特判一下当 k = 1 时,
最小操作次数也必须是 1 才有解。

操作性问题,如果将a一直除以它的因数,那么我们关注操作对象最小基本单元a,一直除以它的因数(a是在不断变化的),然后一直到不能操作为止,我们可以一步直接到1,也可以除以它的最小因子,然后好多次操作之后到1,这样我们就知道了最小操作次数和最大操作次数,便找到了题目的突破口

trick:

1.问刚好k次操作能否满足题目要求,我们可以求出满足题目要求的最小操作次数以及最大操作次数,如果k夹在两者之间,那么可以

2.将数x分解为若干个因数的乘积,最长序列即为质因数分解,因为质数已经不能再分解了

3.分解质因数,如果是用试除法,为O(sqrt(n)),如果n达1e9,那么时间复杂度为4e4,但是如果t比较大,达1e5,那么肯定超时了,此时可以用质数筛,只筛一次,然后直接枚举质数,不用再一个一个枚举因数了,这样可以比之前优化一些,毕竟直接枚举质数是否是因数比一个一个枚举还是快的,但是其实快不了多少,因为时间复杂度看枚举次数,质数的密度还是挺大的,其实欧拉筛的同时是可以求质因数个数的,方法更好,详情看第8题

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int a,b,k;
const int N=5e5+10;
bool st[N];
int prime[N];
int n;
int cnt;
//欧拉筛
void get_prime(int n){
	for(int i=2;i<=n;i++){
		if(!st[i]) prime[cnt++]=i;
		for(int j=0;prime[j]<=n/i;j++){
			st[prime[j]*i]=true;
			if(i%prime[j]==0) break;
		}
	}
}
void solve() {
	cin>>a>>b>>k;
	if(k==1){
		if(a==b){
			cout<<"NO"<<endl;
			return;
		}
	}
	int minn;
	if(a==b) minn=0;
	else if(a%b==0||b%a==0) minn=1;
	else minn=2;
	int sum=0;
	for(int i=0;i<cnt;i++){
		if(prime[i]*prime[i]>a) break;
		if(a%prime[i]==0){
			while(a%prime[i]==0){
				a/=prime[i];
				sum++;
			}
		}
	}
	if(a>1) sum++;
	for(int i=0;i<cnt;i++){
		if(prime[i]*prime[i]>b) break;
		if(b%prime[i]==0){
			while(b%prime[i]==0){
				b/=prime[i];
				sum++;
			}
		}
	}
	if(b>1) sum++;
	if(k>=minn&&k<=sum) cout<<"YES"<<endl;
	else cout<<"NO"<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
	get_prime(N);
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

3.A2. Prefix Flip (Hard Version)

长度为n的01串a和b
操作:选择a的前缀,01反转并且颠倒
最多2*n次操作,使得字符串a变成b,一定有解
将字符串变成给定串,一个字符一个字符变

操作性问题,虽然说操作对象是前缀,但是由于一整段前缀01都有,不可控,所以我们的操作对象基本单元认定为每个字符都一样的前缀,这样才可控

trick:

1.将字符串变成给定串,往往是一个字符一个字符变,因为连续一段不容易精准地将一段字符都修改正确,当然还有一种是对连续一段都是一样的进行操作

2.如果是将01字符串变成给定串,操作是对一段连续的字符进行01反转,可以将字符串全变成0或者全变成1,如果仅仅可以对第一个字符串进行操作,那么将第一个字符串全变成一样的,再将给定串全变成一样的,虽然给定串不能动,但是01反转操作是可逆的,所以可以输出逆操作,此时两个字符串要么是全0,要么是全1,最多整个字符串反转一次

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
string a,b;
void solve() {
	cin>>n;
	cin>>a>>b;
	a=' '+a;
	b=' '+b;
	vector<int>ans1,ans2;
	for(int i=2;i<=n;i++){
		if(a[i]!=a[i-1]) ans1.push_back(i-1);
	}
	for(int i=2;i<=n;i++){
		if(b[i]!=b[i-1]) ans2.push_back(i-1);
	}
	reverse(ans2.begin(),ans2.end());
	vector<int>ans;
	for(int i=0;i<(int)ans1.size();i++) ans.push_back(ans1[i]);
	if(a[n]!=b[n]) ans.push_back(n);
	for(int i=0;i<(int)ans2.size();i++) ans.push_back(ans2[i]);
	cout<<ans.size()<<endl;
	for(int i=0;i<(int)ans.size();i++) cout<<ans[i]<<' ';
	cout<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

4.C. Array Destruction

长度为2 * n的数组a
操作:
首先,找两个元素,然后删除它们,x取它们的max(第一次操作)
然后再找两个元素和为x的,删除它们,x更新为它们的max(n-1次操作)
问能否删除所有元素

操作性问题,操作对象最基本单元为元素,专注于一个元素

与顺序无关,先排个序
一开始选择最小的和最大的,因为最小的没什么用,直接丢掉,最大的可选择空间大,错了
发现肯定是从大到小都需要作为x,而且找的两个数,其中一个是剩下的最大的那个,唯一的变数就是最大的数最开始带的那一个数不需要作为x

数据比较小,可以枚举最大的数带的是哪一个数

操作性问题,操作对象为两个元素,但基本单元仍为一个元素,就研究一个元素就行,排个序之后,从最大的元素开始研究,发现要找的两个数,其中一个一定是剩下的最大的,因为如果不找剩下的最大的,那么后面x就取的小了,然后那个剩下的最大的就永远删不掉了,唯一的变数就是一开始最大的数带的是哪一个数,到次此题规律摸清

trick:

谨慎用mp.erase(),经常删不掉元素,另外不能在遍历mp的时候用mp.erase(),这样会死循环,可以先记下来,然后在循环外面用mp.erase()

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
const int N=1010;
int a[2*N];
int n;
void solve() {
	cin>>n;
	for(int i=1;i<=2*n;i++) cin>>a[i];
	sort(a+1,a+1+2*n);
	for(int i=1;i<=2*n-1;i++){//枚举一开始最大的元素带的是哪一个
		vector<int>ans;
		map<int,int>mp;
		vector<PII>res;
		for(int j=1;j<=2*n;j++){
			if(i==j) continue;
			ans.push_back(a[j]);
			mp[a[j]]++;
		}
		sort(ans.begin(),ans.end());
		reverse(ans.begin(),ans.end());
		int x=ans[0];
		mp[x]--;
		int sum=x+a[i];
		res.push_back({x,a[i]});
		for(int j=1;j<(int)ans.size();j++){
			if(mp[ans[j]]!=0){
				if(mp[x-ans[j]]){
					res.push_back({ans[j],x-ans[j]});
					mp[ans[j]]--;
					mp[x-ans[j]]--;
				}
				else break;
				x=ans[j];
			}
		}
		bool ok=true;
		for(auto v:mp){
			if(v.second!=0){
				ok=false;
				break;
			}
		}
		if(ok){
			cout<<"YES"<<endl;
			cout<<sum<<endl;
			for(auto v:res) cout<<v.first<<' '<<v.second<<endl;
			return;
		}
	}
	cout<<"NO"<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

5.F. Spy-string

一共有n个字符串,每个字符串长度均为m,小写字母
构造一个长度为m的字符串,使得它与n个字符串中任意一个最多有一个字符不同,无解输出-1

数据非常的小,考虑暴力
由于和任意一个字符串最多一个字符不同,那么就单单考虑和第一个字符串的关系
对于第一个字符串,我们构造字符串和它完全相同,或者差一个字符(那个字符变得和后面字符串对应字符相等,同时不能不变),然后去检验和其它字符串不同字符的个数是否大于1
但是这样枚举的不够暴力,枚举也不全,直接枚举a到z

trick:

如果是小写字母字符串,考虑暴力修改的话(只修改一个),可以枚举a到z

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=15;
string s[N];
int n,m;
void solve() {
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>s[i];
	bool ok;
	string tmp;
	for(int p=0;p<m;p++){//枚举修改哪个字符
		for(char ch='a';ch<='z';ch++){
			ok=true;
			tmp=s[1];
			tmp[p]=ch;
			for(int i=1;i<=n;i++){
				int cnt=0;
				for(int j=0;j<m;j++){
					if(tmp[j]!=s[i][j]) cnt++;
				}
				if(cnt>1){
					ok=false;
					break;
				}
			}
			if(ok){
				cout<<tmp<<endl;
				return;
			}
		}
	}
	cout<<-1<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

6.D. Ehab the Xorcist

构造一个数组,使得所有元素的异或和为u,和为v
要求数组长度最短,无解输出-1

遇到异或,总归关于数学,先将有关于异或的式子以及性质全部列出来,根据题目选取有用的

a+b=(a^b)+ 2 * (a&b)

(a&b)&(a^b)= 0

a^a=0

x=aax

a^0=a

一个序列异或起来等于0,将序列分成两个集合,不管怎么分,两个集合内部异或起来得到的结果肯定相等

一个序列的异或和一定小于等于数值和(异或的本质是二进制下的不进位加法,相比起进位的普通加法,所得的结果当然会较小)

一个序列的数值和和异或和奇偶性相同

如果a^b已知=x或者a&b已知=y,那么要确定a和b,只要进行分配1即可

然后如果都没用到的话,大概是单独考虑每一位

trick:

如果要构造数组,要求长度最短,如果样例中只有长度为2和3的,那么基本上长度可以控制在小于等于3

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int u, v;
void solve() {
	cin >> u >> v;
	if (u > v) {
		cout << -1 << endl;
		return;
	}
	if (u == v) {
		if(u==0){
			cout<<0<<endl;
			return;
		}
		cout << 1 << endl;
		cout << u << endl;
		return;
	}
	if (u % 2 != v % 2) {
		cout << -1 << endl;
		return;
	}
	if(u&((v-u)/2)){
		cout<<3<<endl;
		cout<<(v-u)/2<<' '<<(v-u)/2<<' '<<u<<endl;
		return;
	}
	cout<<2<<endl;
	int x = (v - u) / 2, y = (v - u) / 2;
	x |= u;
	cout << x << ' ' << y << endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
//    cin>>t;
	while (t--) {
		solve();
	}
	return 0;
}

7.C. Baby Ehab Partitions Again

长度为n的数组a(数[1,2000])n大于等于2
操作:删除一些元素
使得可以将剩下的所有元素分成两个集合,无论怎么分,两个集合和都不相等,一定有解
问最少删除几个元素

与顺序无关,先排个序
操作性问题,操作对象基本单元为元素,就研究某个元素

样例中只有1和2,猜测可能最多删除一个
如果总和为奇数,那么不用删除
如果总和为偶数,如果不能分成和相等的两组,那么只要有一个奇数,那么只要把这个奇数删除即可
那么如何判断能否分成和相等的两组,可以用01背包,01背包求的是体积为V最多装多少体重的物品,那么我们这里求体积为sum/2看最多装多少,如果刚好等于sum/2,那么可以分成和相等的两组,否则不行
将每个数除以2是和原数组等价的,因为选两个集合和之前选的索引是一样的,所以我们一直除以2,直到出现奇数,选那个奇数就行

操作性问题,研究单个元素,发现如果删除该元素后为奇数,那么就不可能分成两组和一样的,由此有了眉目,主要是少了以下trick,导致后续没思路

trick:
1.问最少删除几个,样例中只有1和2,猜测答案最大为1

2.问能否将序列分成2组和一样的,用01背包,求体积为sum/2最大装和为多大的数字,如果为sum/2那么可以

3.对于将两个序列分成2组和一样的,将整个序列同乘以一个数或同除以一个数是等价的,因为并不影响两个集合和是否相等

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=110,M=2e5+10;
int a[N];
int f[N][M];
int n;
void solve() {
	cin>>n;
	int sum=0;
	for(int i=1;i<=n;i++) cin>>a[i],sum+=a[i];
	if(sum%2){
		cout<<0<<endl;
		return;
	}
	for(int i=1;i<=n;i++){
		for(int j=0;j<=sum/2;j++){
			f[i][j]=f[i-1][j];
			if(j>=a[i]) f[i][j]=max(f[i][j],f[i-1][j-a[i]]+a[i]);
		}
	}
	if(f[n][sum/2]!=sum/2){
		cout<<0<<endl;
		return;
	}
	int minn=2e9,mini=1;
	for(int i=1;i<=n;i++){
		int cnt=0;
		while(a[i]%2==0){
			a[i]/=2;
			cnt++;
		}
		if(cnt<minn){
			minn=cnt;
			mini=i;
		}
	}
	cout<<1<<endl;
	cout<<mini<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

8.D. Soldier and Number Game

对于a的阶乘除以b的阶乘,记为n
求n一直除以因数(n在变动),直到1,问最多除以几次

n等于b+1一直乘到a
我们只要枚举b+1到a,求它们质因数的个数即可,然后全部加起来
由于样例数量过多,每次都重复算,所以全部只算一次,然后前缀和
我一开始用的方法是求用质数筛求出所有质数,然后从1到5e6去分解质因数,分解质因数时间复杂度为O(logn),然后质数的密度也挺大的,所以先直接枚举质数是不是因数和分解质因数其实差不多,都是O(logn),肯定会超时
其实欧拉筛可以同时求质因数的个数

trick:

欧拉筛求质因数个数

const int N=5e6+10;
bool st[N];
int prime[N];
int d[N];
int sum[N];
int cnt;
//欧拉筛
void get_prime(int n){
	for(int i=2;i<=n;i++){
		if(!st[i]) prime[cnt++]=i,d[i]=1;//i为质数,所以i的质因数个数为1
		for(int j=0;prime[j]<=n/i;j++){
			st[prime[j]*i]=true;
			d[i*prime[j]]=d[i]+1;
			if(i%prime[j]==0) break;
		}
	}
}
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=5e6+10;
bool st[N];
int prime[N];
int d[N];
int sum[N];
int cnt;
//欧拉筛
void get_prime(int n){
	for(int i=2;i<=n;i++){
		if(!st[i]) prime[cnt++]=i,d[i]=1;//i为质数,所以i的质因数个数为1
		for(int j=0;prime[j]<=n/i;j++){
			st[prime[j]*i]=true;
			d[i*prime[j]]=d[i]+1;
			if(i%prime[j]==0) break;
		}
	}
}
int a,b;
void solve() {
	cin>>a>>b;
	cout<<sum[a]-sum[b]<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
	get_prime(N);
	for(int i=1;i<=5e6;i++) sum[i]=sum[i-1]+d[i];
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

9.A. Bits

在[l,r]中找一个最小的x,满足它的二进制1的个数最多

要想1最多,而且是最小的,那么就在1个数一样的情况下,位数要少,也就是全1,全1即2的幂次减1,所以只要枚举2的幂次即可,二分小于等于r的最大的(2的幂次-1)
2的幂次一共没多少,先存起来
二分小于等于r的最大的2的幂次-1,如果ans[L]大于等于l,那么答案为ans[L]
否则,二分大于等于r的最小的2的幂次-1,然后求出一共几位1,然后自己构造出比它少1位的数,将某一位1变成0,最多枚举它的位数次,从次高位开始,如果在[l,r]之间,那么就得到答案了,还是不对

这题想复杂了,它要尽可能多的1并且尽可能的小,那么就对l,从低位到高位,如果将0变成1不会超过r,那么就将0变成1

首先l满足在[l,r]中,而且是最小的,从最小的数往上贪心,能以更小的cost贪1就贪1

trick:

比较巧妙,当作典例

如果要二进制的1个数最多,可以贪心 ,从低位到高位,如果能将0变成1就将0变成1

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int l,r;
vector<int>ans;
void solve() {
	cin>>l>>r;
	for(int i=0;i<=60;i++){
		if((l|(1ll<<i))<=r) l|=(1ll<<i);
		else break;
	}
	cout<<l<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

10.C. Helping the Nature

长度为n的数组a(数[-1e9,1e9])
ai表示第i棵树的湿度
操作:
共三种
1.将前缀湿度减1
2.将后缀湿度减1
3.将所有湿度加1
问将所有湿度变为0,最少需要几次操作,一定有解

差分

三种操作可以分别被转化为:

1.d[1]-=1,d[i+1]+=1

2.d[i]-=1

3.d[1]+=1

要使得整个序列变为0,也就是对差分数组求前缀和后的序列全为0,也就是差分数组均变成0,单点修改很容易

操作性题目,当操作对象为前缀后缀等时,用基本单元法分析不太出来,作为该类题目的一个案例

trick:

对于一段连续的区间+x或者-x,想到差分,将区间修改转化为单点修改

构造差分数组:

for(int i=1;i<=n;i++) d[i]=a[i]-a[i-1];//构造差分数
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N];
int d[N];//差分数组
int n;
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) d[i]=a[i]-a[i-1];//构造差分数组
	int ans=0;
	for(int i=2;i<=n;i++){
		if(d[i]<0){
			ans+=(-d[i]);
			d[1]-=(-d[i]);
		}
		else ans+=d[i];
	}
	ans+=abs(d[1]);
	cout<<ans<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

11.C. Pinkie Pie Eats Patty-cakes

一共有n个馅饼
ai不同 ,馅料不同 ,至少有两个馅料相同
重新排列序列,使得相同馅料之间的最小距离最大

对于相同馅料个数最多的,分开,然后把其它的填充到间隔中

trick:

如何快速想到思路,我们可以构造极端的样例,夸大,让某个馅料超级超级多,然后发现起决定因素的是相同馅料最多的那个

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[N];
int n;
void solve() {
	cin>>n;
	map<int,int>mp;
	for(int i=1;i<=n;i++) cin>>a[i],mp[a[i]]++;
	int maxn=0;
	int cnt=0;
	for(auto v:mp) maxn=max(maxn,v.second);//最多的是maxn个,有maxn-1个空
	for(auto v:mp){
		if(v.second==maxn) cnt++;
	}
	int ans=(n-cnt*maxn)/(maxn-1);
	ans+=cnt-1;
	cout<<ans<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

12.C. Link Cut Centroids

操作 :删去一条边 ,再添加一条边(可以添加删去的那条边)
使得重心是唯一的,一定有解
重心:删去这个点以及和它相连的边,得到的最大连通块的大小最小

考察树的重心

trick:

1.树的重心最多只有两个

2.树形dp求树的重心

bool vis[N];
int n;
int ans1=2e9,ans2=2e9;//ans1记录最小,ans2记录次小
int t1,t2;
vector<vector<int>>e(N);
int dfs(int u){
	vis[u]=true;//标记被搜过
	int size=0;//记录u的最大子树的节点数
	int sum=1;//记录以u为根的子树的节点数
	for(auto v:e[u]){
		if(vis[v]) continue;//避免向上搜索
		int s=dfs(v);//s是以j为根的子树的节点数
		size=max(size,s);//记录u的最大子树的节点数
		sum+=s;//累加u的各个子树的节点数
	}
	size=max(size,n-sum);
	if (size < ans2) {//比次小小才更新最小和次小
		if (size <= ans1) {//size小于等于最小,那么更新次小,再更新最小
			ans2 = ans1;
			t2 = t1;
			ans1 = size;
			t1 = u;
		} else//size比次小小,但是比最小大,只更新次小
			ans2 = size, t2 = u;
	}
	return sum;//返回以u为根的子树的节点数
}
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
bool vis[N];
int n;
int ans1=2e9,ans2=2e9;//ans1记录最小,ans2记录次小
int t1,t2;
vector<vector<int>>e(N);
int dfs(int u){
	vis[u]=true;//标记被搜过
	int size=0;//记录u的最大子树的节点数
	int sum=1;//记录以u为根的子树的节点数
	for(auto v:e[u]){
		if(vis[v]) continue;//避免向上搜索
		int s=dfs(v);//s是以j为根的子树的节点数
		size=max(size,s);//记录u的最大子树的节点数
		sum+=s;//累加u的各个子树的节点数
	}
	size=max(size,n-sum);
	if (size < ans2) {//比次小小才更新最小和次小
		if (size <= ans1) {//size小于等于最小,那么更新次小,再更新最小
			ans2 = ans1;
			t2 = t1;
			ans1 = size;
			t1 = u;
		} else//size比次小小,但是比最小大,只更新次小
			ans2 = size, t2 = u;
	}
	return sum;//返回以u为根的子树的节点数
}
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) e[i].clear();
	memset(vis,false,sizeof vis);
	int a,b;
	for(int i=0;i<n-1;i++){
		int x,y;
		cin>>x>>y;
		a=x,b=y;
		e[x].push_back(y);
		e[y].push_back(x);
	}
	ans1=2e9,ans2=2e9;
	dfs(1);
//	cout<<ans1<<' '<<ans2<<endl;
//	cout<<t1<<' '<<t2<<endl;
	if(ans1!=ans2){
		cout<<a<<' '<<b<<endl;
		cout<<a<<' '<<b<<endl;
	}
	else{
		for(auto v:e[t1]){
			if(v!=t2){
				cout<<v<<' '<<t1<<endl;
				cout<<v<<' '<<t2<<endl;
				return;
			}
		}
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

13.D. Vasya and Chess

博弈

自己模拟,找规律

镜像对称

当n为偶数时,后手可以模仿对手

当n为奇数时,先手先走一步,然后可以一直模仿后手

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
	cin>>n;
	n-=2;
	if(n%2==0){
		cout<<"white"<<endl;
		cout<<1<<' '<<2<<endl;
	}
	else cout<<"black"<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

14.C. The Third Problem

mex值为x,则1到x-1必须有,x不能有
0的位置不能变,假设0的下标为i,则[i,i]的mex值为1,如果改变0的位置,那么[i,i]的mex值变为0
1的位置也不能变,假设1的下标为j,则[i,j]的mex值大于等于2,如果改变1的位置,假设改为k,如果k<j,那么原先[i,k]的mex值为1,现在[i,k]的mex值大于等于2,如果k>j,那么原先[i,k]的mex值大于等于2,现在[i,k]的mex值为1

从小到大枚举mex值,维护[l,r],如果数mex在前面维护的[l,r]内,那么数mex可以在区间内和没确定位置的数交换,[l,r]mex值不会变,且任意一个子段mex值不会变,如果数mex不在[l,r]内,那么数mex位置不能变,更新扩大[l,r]

作为mex类题目的一个案例

trick:

1.mex值为x,则1到x-1必须有,x不能有

2.如果所有数均不同(比如全排列),可以预处理每个数的位置,一个数对应一个位置

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10,mod=1e9+7;
int a[N];
int n;
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	map<int,int>pos;
	for(int i=1;i<=n;i++) pos[a[i]]=i;//预处理每一个数的位置,所有数均不同,一个数对应一个位置
	int l=pos[0],r=pos[0];
	int ans=1;
	for(int i=1;i<n;i++){
		if(pos[i]>l&&pos[i]<r) ans=(ans*(r-l+1-i))%mod;
		else{
			l=min(l,pos[i]);
			r=max(r,pos[i]);
		}
	}
	cout<<ans<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

15.D. Traps

一共有n个陷阱
ai表示第i个陷阱的基础伤害值
按顺序走,最多可以跳过k个陷阱,但是每跳过一个陷阱,那么后面的每个陷阱伤害+1
问最小总伤害

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

参考CF1684D题解 - qscisQJing 的博客 - 洛谷博客 (luogu.com.cn)

trick:

如果想不出思路的话,可以借助数学,列式子(设未知数或题目中原本就有的未知数,列含未知数的式子),然后通过中间的化简,得到简洁的式子,最终得出答案

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int n,k;
int b[N];
struct node{
	int a,idx;
	bool operator<(const node &W)const{
		return a+idx>W.a+W.idx;
	}
}q[N];
void solve() {
	cin>>n>>k;
	int sum=0;
	for(int i=1;i<=n;i++) cin>>q[i].a,sum+=q[i].a,q[i].idx=i,b[i]=q[i].a;
	sort(q+1,q+1+n);
	map<int,int>mp;
	for(int i=1;i<=k;i++){
		mp[q[i].idx]=1;
	}
	int cnt=0;
	for(int i=1;i<=n;i++){
		if(mp[i]){//跳过
			sum-=b[i];
			cnt++;//后面每个伤害加cnt
		}
		else sum+=cnt;
	}
	cout<<sum<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

16.D. Ceil Divisions

长度为n的数组a
ai=i
操作:选择两个数,其中一个数修改为它除以另一个数,向上取整
操作最多n+5次
使得数组a变成1个2,其它都是1,一定有解

从后往前,n到3,都和它的前一个进行操作,都变成2,然后,再从n到3,都和它的前一个进行操作,都变成1,这样是2*(n-2)次操作
但是最多操作5次
可以让n除以n-1变成2,n-1除以n-2变成2,然后从2开始到n-1,i除以i+1,都变成1,这样就差n没有变成1了,然后一直用n除以2,但是n最高达2e5,最多除以18次,肯定超了,于是改成8,8先不变成1,然后n一直除以8,最多除以6次,然后8除以2共3次,所以一共n-4+6+3=n+5次

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
int n;
void solve() {
	cin>>n;
	vector<PII>ans;
	if(n<=8){
		for(int i=3;i<=n-1;i++){
			ans.push_back({i,i+1});
		}
		ans.push_back({n,2});
		ans.push_back({n,2});
		ans.push_back({n,2});
	}
	else{
		for(int i=3;i<=7;i++) ans.push_back({i,i+1});
		for(int i=9;i<=n-1;i++) ans.push_back({i,i+1});
		int x=n;
		while(x){
			ans.push_back({n,8});
			x/=8;
		}
		ans.push_back({8,2});
		ans.push_back({8,2});
		ans.push_back({8,2});
	}
	cout<<ans.size()<<endl;
	for(auto v:ans) cout<<v.first<<' '<<v.second<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

17.C. Shinju and the Lost Permutation

给定一个长度为n的数组c
ci是排列p循环右移i-1次的幂次
幂次:排列前缀最大值数组中不同元素的个数

幂次为x就表示排列从第一个开始,上升子序列长度为x
如果幂次为1的话,那么第一个数肯定是最大的数,同时一定存在幂次为1,且只能有1个,这是最关键的一点,这点想到了,但是后面就想不到了

下一步有点巧妙

然后将幂次为1放在最前面,其它按照循环右移的方法放好,然后可以发现,后面的数最多比前面的数大1,因为不可能一下子增加两个,变小倒是可以做到,只要不存在后面的数比前面的数大1以上,我们就有办法构造出

tirck:

构造一个序列,我们可以想什么情况下肯定不满足构造要求,会提供灵感

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[N];
int n;
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	map<int,int>mp;
	for(int i=1;i<=n;i++) mp[a[i]]++;
	if(mp[1]!=1){
		cout<<"NO"<<endl;
		return;
	}
	int pos;
	for(int i=1;i<=n;i++){
		if(a[i]==1){
			pos=i;
			break;
		}
	}
	vector<int>ans;
	for(int i=pos;i<=n;i++) ans.push_back(a[i]);
	for(int i=1;i<=pos-1;i++) ans.push_back(a[i]);
	for(int i=1;i<(int)ans.size();i++){
		if(ans[i]-ans[i-1]>1){
			cout<<"NO"<<endl;
			return;
		}
	}
	cout<<"YES"<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

18.D. Harmonious Graph

n个顶点,m条边的无向图
和谐:如果l可以到达r,那么l也可以到达l+1,l+2,…r-1
问最少加几条边使得图形和谐

并查集,始终维护根节点是编号最大的,然后如果某个连通块最大编号和最小编号直接的编号的根节点超过了该连通块最大编号,那么就需要将该连通块连向那个更大的节点,合并连通块

trick:

连通块维护根节点编号最大

int a=find(u);
int b=find(v);
if(a>b) swap(a,b);
p[a]=b;
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int p[N];
int n,m;
int find(int x){
	if(p[x]!=x) p[x]=find(p[x]);
	return p[x]; 
}
void solve() {
	cin>>n>>m;
	for(int i=1;i<=n;i++) p[i]=i;
	for(int i=0;i<m;i++){
		int u,v;
		cin>>u>>v;
		int a=find(u);
		int b=find(v);
		if(a>b) swap(a,b);
		p[a]=b;
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		int x=find(i);
		while(i<x){
			int y=find(i);
			if(x!=y){
				ans++;
				if(x<y) swap(x,y);
				p[y]=x;
			}
			i++;
		}
	}
	cout<<ans<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

19.E. MEX and Increments

长度为n的数组a(数[0,n])
操作:选择一个元素,加1,可以多次选择同一个索引
问使得mex值为i,最少几次操作,i从0到n,如果i无解,输出-1

要使得mex值为x,则0到x-1都要有,x不能有
对于i,我们需要补i-1这个数,并删去数i,每次我们把多余的i-1放入multiset降序里,这样multiset放入的就是多余的小于i的,如果i-1不存在,我们就用这里面的最大的数去补i-1,然后删去i

trick:

1.mex值为x,则1到x-1必须有,x不能有

2.要想储存小于i的所有数,可以一边遍历一边放i-1,这样始终储存的是小于i的所有数

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N = 2e5 + 10;
int a[N];
int ans[N];
int n;
void solve() {	
	cin >> n;
	map<int, int>mp;
	for (int i = 1; i <= n; i++) cin >> a[i],mp[a[i]]++;
	multiset<int, greater<int>>s;
	ans[0] = mp[0];
	int sum = 0;
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j < mp[i - 1] - 1; j++) s.insert(i - 1);
		//补i-1这个数
		if(mp[i-1]) ans[i]=sum+mp[i];
		else{
			if (s.size() == 0) {
				for (int j = i; j <= n; j++) ans[j] = -1;
				break;
			} else {
				int res = i - 1 - (*s.begin());
				sum += res;
				ans[i] = sum + mp[i];
				s.erase(s.begin());
			}
		}
	}
	for (int i = 0; i <= n; i++) cout << ans[i] << ' ';
	cout << endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
	cin >> t;
	while (t--) {
		solve();
	}
	return 0;
}

20.Problem - 1268A - Codeforces

共n位,n位数字组成一个数x
找到大于等于x的最小的beautiful数
beautiful:k个一循环

要么前k个一直循环,要么前k个+1进位,再一直循环,无非这两种情况,分别检验即可

tirck:

对于可能出现的所有情况(一般不多)都构造出来,然后分别检验,这样不容易出错

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n, k;
string x;
void solve() {
	cin >> n >> k;
	cin >> x;
	string tmp;
	for (int i = 0; i < k; i++) tmp += x[i];
	string ans1;
	for (int i = 0; i < n / k; i++) ans1+=tmp;
	for (int i = 0; i < n % k; i++) ans1+=tmp[i];
	if(ans1>=x){
		cout<<n<<endl;
		cout<<ans1<<endl;
		return;
	}
	//first k加1进位
	int len = tmp.size();
	int pos = len - 1;
	if (tmp[pos] == '9') {
		tmp[pos] = '0';
		pos--;
		while (pos >= 0) {
			if (tmp[pos] == '9') {
				tmp[pos] = '0';
			} else {
				tmp[pos] = (char)(tmp[pos] + 1);
				break;
			}
			pos--;
		}
	} else tmp[pos] = (char)(tmp[pos] + 1);
	cout << n << endl;
	for (int i = 0; i < n / k; i++) cout << tmp;
	for (int i = 0; i < n % k; i++) cout << tmp[i];
	cout << endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
//    cin>>t;
	while (t--) {
		solve();
	}
	return 0;
}

一开始是按以下方法写的,就是判断后面有没有大于前k个的,如果有,那么就是前k个+1进位一直循环,否则就是前k个一直循环

但是这样不知道哪里错了

第一种方法对于可能出现的所有情况(一般不多)都构造出来,然后分别检验,这样不容易出错

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,k;
string x;
void solve() {
	cin>>n>>k;
	cin>>x;
	string tmp;
	for(int i=0;i<k;i++) tmp+=x[i];
	bool ok=false;
	for(int i=k;i<n/k*k;i+=k){
		string s;
		for(int j=0;j<k;j++){
			s+=x[i+j];
		}
		if(s>tmp){
			ok=true;
			break;
		}
	}
	int j=0;
	for(int i=n/k*k;i<n;i++){
		if(x[i]>tmp[j++]){
			ok=true;
			break;
		}
	}
	if(ok){//后面存在大于first k的,first k加1进位
		int len=tmp.size();
		int pos=len-1;
		if(tmp[pos]=='9'){
			tmp[pos]='0';
			pos--;
			while(pos>=0){
				if(tmp[pos]=='9'){
					tmp[pos]='0';
				}
				else{
					tmp[pos]=(char)(tmp[pos]+1);
					break;
				}
				pos--;
			}
		}
		else tmp[pos]=(char)(tmp[pos]+1);
	}
//	cout<<tmp<<endl;
	cout<<n<<endl;
	for(int i=0;i<n/k;i++) cout<<tmp;
	for(int i=0;i<n%k;i++) cout<<tmp[i];
	cout<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

21.A. Johnny and Contribution

这题题意好绕,给定n 个点 m 条边的图,你需要给每个点写上数字,其数字为所有与它相邻且被写上数字的点中没出现过的最小的正整数。请求出一种写数字的顺序使得编号为 i 的点上的数字为 t i t_i ti,无解输出 -1

没出现过的最小的正整数,和mex类似,优先看小的,那么我们从最小的数字开始赋值,这个逻辑在于要想某个点赋值为x,那么它的相邻节点必须有1,2,…x-1,同时不能存在x

那我们就从最小的数字开始赋值,这样,当我们要给某个点赋数字x的时候,我们已经把需要赋小于数字x的点全赋值好了,前面赋值的点都已经确定了,满足预期,然后没有赋值的点下一步赋值也确定了,当枚举到i时,如果预期赋数字i的点不能赋i,那么无解

trick:

mex优先看小的,如果需要构造的话,则从小的往大的构造

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=5e5+10;
int num[N];
int n,m;
void solve() {
	cin>>n>>m;
	vector<vector<int>>e(n+1);
	for(int i=0;i<m;i++){
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	for(int i=1;i<=n;i++) num[i]=1;
	vector<vector<int>>col(n+1);
	for(int i=1;i<=n;i++){
		int c;
		cin>>c;
		col[c].push_back(i);
	}
	vector<int>ans;
	for(int i=1;i<=n;i++){
		for(auto u:col[i]){
			if(num[u]!=i){
				cout<<-1<<endl;
				return;
			}
			for(auto v:e[u]){
				if(num[v]==i) num[v]++;
 			}
			ans.push_back(u);
		}
	}
	for(auto v:ans) cout<<v<<' ';
	cout<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

22.Problem - C - Codeforces

长度为10的01串
第i个字符为1表示有i公斤重的砝码(无限个),
问能否将m个砝码放到天平上,第一个放左,第二个放右,第三个放左,…要求每放一个,天平必须倾斜到另一边

数据比较小,直接暴搜,虽然时间复杂度最高达 1 0 m 10^m 10m,但是步骤有限,可能不可能的分支几步就进行不下去,时间复杂度不会很高

trick:

dfs暴搜,如果找到了解,return不出去,直接exit(0)

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1010;
int ans[N];
string s;
int m;
map<int,int>mp;
void dfs(int u,int suml,int sumr,int last){
	if(u==m+1){
		cout<<"YES"<<endl;
		for(int i=1;i<=m;i++) cout<<ans[i]<<' ';
		cout<<endl;
		exit(0);
	}
	for(int i=1;i<=10;i++){
		if(!mp[i]) continue;
		if(i==last) continue;//不能连续两次放相同重量
		if(u%2==1){//第奇数次放左边
			if(suml+i>sumr){
				ans[u]=i;
				dfs(u+1,suml+i,sumr,i);
			}
		}
		else{
			if(sumr+i>suml){
				ans[u]=i;
				dfs(u+1,suml,sumr+i,i);
			}
		}
	}
}
void solve() {
	cin>>s;
	cin>>m;
	mp.clear();
	for(int i=1;i<=10;i++){
		if(s[i-1]=='1') mp[i]=1;
	}
	dfs(1,0,0,-1);
	cout<<"NO"<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

23.D. Walk on Matrix

参考https://blog.csdn.net/weixin_44164153/article/details/105243404

我们先分析上面的动态规划过程,这个过程的基本思想就是用相邻最大的&值作为当前位置的&值,然而这样的方法是最优的吗?显然不是,因为最大的&值有可能在和其他值&时比一个较小的&值和其他值&时得出来的结果还要小;
所以我们构造的思路就是,我们要让一个看上去更大的值和最终的格子&,然而&的结果却是0,但是在之前格子若是取较小的那个和最终格子&结果反而是k;

trick:

构造题,往往不走常规路,都是取巧,另辟蹊径,往最特殊的情况构造

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int k;
void solve() {
	cin>>k;
	int x=(1ll<<18)-1;
	int y=(1ll<<17);
	cout<<2<<' '<<3<<endl;
	cout<<x<<' '<<k<<' '<<0<<endl;
	cout<<y<<' '<<x<<' '<<k<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

24.C. Manipulating History

一开始是一个长度为1的字符串s
n次操作:第2i-1次操作,在s中选择一个非空子串t[2i-1],用t[2i]替换t[2i-1]
然后给定n次操作之后的字符串s,问最初的字符串(长度为1)是什么

替换包含两步:删除+插入

字符串无非由字符组成,研究单个字符即可
第一步:删除原始字符
第二步:插入字符串
第三步:删除字符串
第四步:插入字符串
最后一步:最终字符串
第一步不看,对于插入的某个字符,要么在删除操作中字符对应,要么在最终字符串中字符对应
所以除了原始字符外,其它字符都为偶数

trick:

1.替换操作等价于删除+插入

2.操作类题目,操作对象为字符串,基本单元为单个字符,研究单个字符

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
	cin>>n;
	map<char,int>mp;
	for(int i=0;i<2*n+1;i++){
		string s;
		cin>>s;
		for(int j=0;j<(int)s.size();j++) mp[s[j]]++;
	}
	for(auto v:mp){
		if(v.second%2){
			cout<<v.first<<endl;
			return;
		}
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

25.F. Equate Multisets

长度为n的数组a和数组b
操作:在b中选择一个元素,乘以2或者除以2(下取整)
操作次数不限
问能否使得b和a相等(无关顺序)

和顺序无关,可以先排个序,也可以不排序
操作性题目,操作对象基本单元为单个元素,专注于单个元素
操作*2是没有精度问题的,操作除以2可能有精度问题,不能乱除
对于a中的每个数,一直除以2直到是奇数,然后记录最终结果,放入map里,并记录每个数的个数
对于b中的数,一直除以2直到出现map里记录的数,然后该数的个数–,最终检查以下是否map里的数的个数都为0

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N],b[N];
int n;
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) cin>>b[i];
	map<int,int>mp;
	for(int i=1;i<=n;i++){
		while(a[i]%2==0){
			a[i]/=2;
		}
		mp[a[i]]++;
	}
	for(int i=1;i<=n;i++){
		while(!mp[b[i]]&&b[i]!=1){
			b[i]/=2;
		}
		mp[b[i]]--;
	}
	for(auto v:mp){
		if(v.second!=0){
			cout<<"NO"<<endl;
			return;
		}
	}
	cout<<"YES"<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

26.B. Neutral Tonality

长度为n的数组a,长度为m的数组b
将数组b的所有元素插入到数组a当中,问最长上升子序列长度最小是多少

首先,一定可以挑出原本的数组a,这个阻止不了,唯一能做的就是尽量让插入的数不产生更长的最长上升子序列
对于数组a,使得以ai为起点的最长上升子序列都不增加,则最长上升子序列不增加
枚举数组a,在ai前面降序放剩余的大于等于ai的数,最后把剩余的降序输出

trick:

有时候,要想整体怎么样,可以通过让所有局部怎么样来使得整体怎么样

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N],b[N];
int n,m;
void solve() {
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	multiset<int,greater<int>>s;
	for(int i=1;i<=m;i++){
		int x;
		cin>>x;
		s.insert(x);
	}
	for(int i=1;i<=n;i++){
		while(s.size()&&*s.begin()>=a[i]){
			cout<<(*s.begin())<<' ';
			s.erase(s.begin());
		}
		cout<<a[i]<<' ';
	}
	while(s.size()){
		cout<<*s.begin()<<' ';
		s.erase(s.begin());
	}
	cout<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

27.A. Alyona and mex

构造一个长度为n的数组a(数[0,1e9])
给定m个区间[l,r],每个区间都有一个mex值,使得最小的mex值最大

首先,mex值非常地看区间长度,比如说要想mex为5,必须有0,1,2,3,4,同时不能有5,如果区间长度为2,那不可能mex值为5,mex值做多为2
所以,看最短的区间,使得mex值尽可能大,从0开始依次递增填,使得mex值等于区间长度,只要从头开始一直这样循环就行了

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N];
int n,m;
void solve() {
	cin>>n>>m;
	int len=2e9;
	vector<int>ans;
	for(int i=0;i<m;i++){
		int l,r;
		cin>>l>>r;
		int len1=r-l+1;
		if(len>len1){
			ans.clear();
			ans.push_back(l);
			len=len1;
		}
		else if(len==len1) ans.push_back(l);
	}
	vector<int>res;
	for(int i=0;i<len;i++) res.push_back(i);
	int cnt=0;
	for(int i=1;i<=n;i++){
		a[i]=res[cnt];
		cnt=(cnt+1)%(int)res.size();
	}
	cout<<len<<endl;
	for(int i=1;i<=n;i++) cout<<a[i]<<' ';
	cout<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

28.A. GCD Table

构造一个长度为n的数组a,它的矩阵gcd共n*n个数,给出打乱顺序的
矩阵,关于y=x对称,除了对角线的(即为要构造的数组a),其它都是成对的
对于n * n个数,去掉成对的,剩下n个数即为答案
当然,有可能对角线上有相同的数,得再改进一下
首先,个数为奇数的数肯定作为答案
然后如果不足n个,那么补足(两个两个补)
感觉补足不是完全对

gcd(a,b)<=min(a,b),第一大的数和第二大的数肯定是答案,然后剩下的最大的是答案,但是每次确定一个答案,需要将其和前面的记录的答案gcd,将产生的数删掉两个

trick:

gcd(a,b)<=min(a,b)

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
int gcd(int a,int b){
	if(b==0) return a;
	return gcd(b,a%b);
}
void solve() {
	cin>>n;
	map<int,int>mp;
	vector<int>ans;
	for(int i=0;i<n*n;i++){
		int x;
		cin>>x;
		ans.push_back(x);
		mp[x]++;
	}
	sort(ans.begin(),ans.end());
	reverse(ans.begin(),ans.end());
	vector<int>res;
	res.push_back(ans[0]);
	mp[ans[0]]--;
	for(int i=1;i<(int)ans.size();i++){
		if(mp[ans[i]]){
			for(int j=0;j<(int)res.size();j++){
				mp[gcd(res[j],ans[i])]-=2;
			}
			res.push_back(ans[i]);
			mp[ans[i]]--;
		}
	}
	for(int i=0;i<(int)res.size();i++) cout<<res[i]<<' ';
	cout<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

29.D. Mathematical Problem

给定一个奇数n
构造n个不同的平方数,n个数字的位数都得相同,不能有前导0,且组成数字必须相同
一定有解

先打表,把平方数都打出来
长度为n,n高达99,肯定爆了,所以这题应该是构造题,找规律题

n=5
16900
10609
96100
90601
19600
n=7
1690000
1060900
1006009
9610000
9060100
9006001
1960000

trick:

169是平方数,等于13*13,并没有进位,如果没有进位的话

13*13=169
130*130=16900
103*103=10609
1003*1003=1006009
以此类推,没有发生进位,可以在中间夹相等数量的0,仍然是平方数
同理
31*31=961
310*310=96100
301*301=90601
3001*3001=9006001
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
	cin>>n;
	if(n==1){
		cout<<1<<endl;
		return;
	}
	int x=n-3;//0的个数
	for(int i=0;i<=x/2;i++){//中间夹几个0
		cout<<1;
		for(int j=1;j<=i;j++) cout<<0;
		cout<<6;
		for(int j=1;j<=i;j++) cout<<0;
		cout<<9;
		for(int j=1;j<=x-i*2;j++) cout<<0;
		cout<<endl;
	}
	for(int i=0;i<=x/2;i++){//中间夹几个0
		cout<<9;
		for(int j=1;j<=i;j++) cout<<0;
		cout<<6;
		for(int j=1;j<=i;j++) cout<<0;
		cout<<1;
		for(int j=1;j<=x-i*2;j++) cout<<0;
		cout<<endl;
	}
	cout<<1<<9<<6;
	for(int i=0;i<x;i++) cout<<0;
	cout<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

30.D. Array Painting

长度为n的数组a(数0,1,2)初始全是蓝色的
操作:
二选一
1.花费1硬币将一个蓝色涂成红色
2.对于涂成红色的不等于0的一个,数值减1,并将它相邻的蓝色涂成红色
使得所有颜色为红色,问最少花费多少硬币

优先将数值不为0的变成红色 ,这样就可以免费涂相邻的了
优先涂2的,然后就可以把相邻的都免费涂成红色,然后产生连锁反应

对于连续的非0 ,如果全都是1,那么就可以和一边一起花费一个硬币(优先涂左边,因为是从左往右遍历的),如果有一个2,那么两边一起

对于剩余的,一个花费一个硬币

trick:

求连续非0区间:

vector<PII>ans;
	bool ok = false;
	int l, r;
	for (int i = 1; i <= n; i++) {
		if (a[i] && !ok) {
			l = i;
			ok = true;
		} else if (a[i] == 0 && ok) {
			r = i - 1;
			ans.push_back({l, r});
			ok = false;
		}
	}
	if (ok) ans.push_back({l, n});
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int, int>PII;
const int N = 2e5 + 10;
int a[N];
int n;
void solve() {
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i];
	vector<PII>ans;
	bool ok = false;
	int l, r;
	for (int i = 1; i <= n; i++) {
		if (a[i] && !ok) {
			l = i;
			ok = true;
		} else if (a[i] == 0 && ok) {
			r = i - 1;
			ans.push_back({l, r});
			ok = false;
		}
	}
	if (ok) ans.push_back({l, n});
	int res = 0;
	map<int, int>mp;
	for (auto v : ans) {
		int l = v.first, r = v.second;
		bool flag=false;
		for (int i = l; i <= r; i++) {
			mp[i] = 1;
			if (a[i]==2) flag=true;
		}
		if(flag) mp[l-1]=mp[r+1]=1;
		else{
			if(!mp[l-1]&&l-1>=1) mp[l-1]=1;
			else mp[r+1]=1;
		}
		res++;
	}
	for (int i = 1; i <= n; i++) {
		if (!mp[i]) res++;
	}
	cout << res << endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
//    cin>>t;
	while (t--) {
		solve();
	}
	return 0;
}

31.C. Bakry and Partitioning

n个节点的树,n-1条边
第i个节点的权值是ai

至少删1条边,最多删k-1条边,问能否使得所有连通块异或和相等

如果全部异或和等于0,那我们可以肯定可以分成两个异或和相等的连通块,只要一个节点单独成一个连通块,剩余的成一个连通块
如果异或和不等于0,那么要想每个连通块异或和相等,然后全部异或和又不等于0,那么肯定是奇数个连通块,异或和均为x,奇数个x的异或和也为x,所以dfs一遍,当异或和为x就成一个连通块,如果最终连通块的数量大于等于3,那么一定可以分成刚好三个一样的

trick:

1.对于某个连通块异或和为tmp,则成为一个连通块:

注意,递归函数不要用外面的变量(除了统计个数以外),这里用到now,在递归函数内部定义

int dfs(int u, int fa) { // 返回当前子树异或和
	int now = a[u]; // 统计当前子树异或和
	for (auto v : e[u]) {
		if (v == fa) continue;
		now ^= dfs(v, u);
	}
	if (now == tmp) { //该子树满足条件, 拆分部分++, 并删除该子树
		all++;
		return 0;
	}
	return now;
}

不能这样写,因为dfs本质上是搜相邻节点,进栈出栈,每次搜的是一条链,当一条链搜完后,退栈,直到分叉点,然后再搜相邻节点到另一条链,搜另一分支时,前面已经都退栈了,前一分支的信息和这一分支的信息没关系了

void dfs(int u,int fa){
	sum^=a[u];
	if(sum==tmp){
		sum=0;
		cnt++;
	}
	for(auto v:e[u]){
		if(v==fa) continue;
		dfs(v,u);
	}
}

2.一个序列的异或和为0,那么任意分成两个序列,异或和相等

3.如果每个连通块的异或和为x,一共有5个连通块,全部异或和为x,那么我们完全可以分成3个异或和为x的连通块

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N = 1e5 + 10;
int a[N];
int n, k;
vector<int>e[N];
int all;
int tmp;
int dfs(int u, int fa) { // 返回当前子树异或和
	int now = a[u]; // 统计当前子树异或和
	for (auto v : e[u]) {
		if (v == fa) continue;
		now ^= dfs(v, u);
	}
	if (now == tmp) { //该子树满足条件, 拆分部分++, 并删除该子树
		all++;
		return 0;
	}
	return now;
}
void solve() {
	cin >> n >> k;
	for (int i = 1; i <= n; i++) cin >> a[i];
	tmp = 0;
	for (int i = 1; i <= n; i++) e[i].clear(), tmp ^= a[i];
	for (int i = 0; i < n - 1; i++) {
		int u, v;
		cin >> u >> v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	if (tmp == 0) {
		cout << "YES" << endl;
		return;
	}
	all = 0;
	dfs(1, 0);
	if (all >= 3 && k >= 3) cout << "YES" << endl;
	else cout << "NO" << endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
	cin >> t;
	while (t--) {
		solve();
	}
	return 0;
}

32.D1. RPD and Rap Sheet (Easy Version)

交互题
给定n和k(k为2)
共n次猜测机会,猜x(x在[0,n-1]),如果猜对了,交互器输出1,如果猜错了,交互器输出0,同时x变为x^我们猜的数字y

一共有n次机会,最多n个数字,完全可以一个一个猜,第一次猜0,之后猜i^(i+1),每次消除前面猜的数对之后的影响

作为交互题和异或题的案例

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,k;
void solve() {
	cin>>n>>k;
	cout<<0<<endl;
	cout.flush();
	int x;
	cin>>x;
	if(x==1) return;
	for(int i=1;i<n;i++){
		cout<<(i^(i-1))<<endl;
		cout.flush();
		cin>>x;
		if(x==1) return;
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

33.D. Boris and His Amazing Haircut

长度为n的数组a和b
ai表示位置i的头发的高度
bi表示理发之后位置i的头发的高度
共有m把剃刀,每把剃刀最多用一次
操作:对于一把没有用过的剃刀x,对于区间[l,r],区间所有数的数修改为ai和x中的最小值
问能否使得a数组变成b数组

对于问能否怎么样,输出YES或NO,先考虑不可能的情况
如果ai<bi,无解,因为头发不可能越剃越长
看区间中的最大元素maxn,如果maxn小于等于剃刀的大小x,则该整个区间可一起剃,否则分开剃
区间最大值用ST表预处理

trick:

1.区间最值用ST表预处理

map<int, vector<int>> e;
可以直接e[x].push_back(y)
另外,可以直接for (auto [x, pos] : e),pos表示[x]

3.看区间中的最大元素maxn,如果maxn小于等于剃刀的大小x,则该整个区间可一起剃,否则分开剃的代码:

for (auto [x, pos] : e) {
		int cnt = 1;
		for (int i = 0; i  < (int)pos.size()-1; i++) {
			if (query(pos[i], pos[i + 1]) != x) cnt++;
		}
		if (cnt > mp[x]) {
			cout << "NO" << endl;
			return;
		}
	}
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N = 2e5 + 10, M = 18;
int a[N], b[N];
int n, m;
int f[N][M];
void init() {
	//预处理,j表示区间长度取log,i表示区间左端点,i+2^j表示区间右端点
	for (int j = 0; j < M; j++) {
		for (int i = 1; i + (1 << j) - 1 <= n; i++) {
			if (!j) f[i][j] = b[i];
			else f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
			//区间dp,将左端点为i,区间长度为2^j的区间分为左右两边,则两边长度均为2^(j-1)
			//右半边的左端点为i+2^(j-1)
		}
	}
}
int query(int l, int r) {
	int len = r - l + 1;
	int k = log(len) / log(2);
	return max(f[l][k], f[r - (1 << k) + 1][k]);
}
void solve() {
	cin >> n;
	map<int, vector<int>> e;
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= n; i++) {
		cin >> b[i];
		if (b[i] < a[i]) e[b[i]].push_back(i);
	}
	init();
	map<int, int>mp;
	cin >> m;
	for (int i = 1; i <= m; i++){
		int x;
		cin>>x;
		mp[x]++;
	}
	for (int i = 1; i <= n; i++) {
		if (a[i] < b[i]) {
			cout << "NO" << endl;
			return;
		}
	}
	for (auto [x, pos] : e) {
		int cnt = 1;
		for (int i = 0; i  < (int)pos.size()-1; i++) {
			if (query(pos[i], pos[i + 1]) != x) cnt++;
		}
		if (cnt > mp[x]) {
			cout << "NO" << endl;
			return;
		}
	}
	cout << "YES" << endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
    cin>>t;
	while (t--) {
		solve();
	}
	return 0;
}

34.D. The Number of Imposters

n个人玩游戏,n个人编号分别为1到n
共m条评论,i,j,c
i说j是c这个角色(imposter或者crewmate)
每个人只拥有一个角色
imposter总是说假话,crewmate总是说真话
问最多有几个imposter,无解,输出-1

参考CF1594D The Number of Imposters 题解 - leexzq的博客 - 洛谷博客 (luogu.com.cn)

我们想想每条对话代表什么:

  • 如果一个人说另一个人是杀手,则他们属于不同阵营(互踩)
  • 如果一个人说另一个人是好人,则他们属于相同阵营(发金水)

而很明显这些关系是可以传递的,而每个人之间的关系又有多种,所以我们可以想到扩展域并查集(带标签的并查集)。

用两个点表示第 i 个人,其中第 i 号点表示与他身份相同的人的集合(正集),第 i*+*n号点表示与他身份不同的人的集合(反集)。

对每一条对话,若说别人为好人,合并他们的正反集与对方的合并,否则把他们的正集与对方的反集合并,最后判断对立的两个集合大小,取较大值求和即可。

同一阵营的放在同一个集合中

trick:

1.严重错误:输入的时候return,可以先将输入存储,然后重新遍历,然后可以return

2.只有两个对立的阵营,那么同一阵营的合并在一个集合中,为了齐全,对于某个点i,定义它的反集为i+n,这样就可以使得情况全部覆盖到

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10,M=5e5+10;
int p[N*2];
int siz[N*2];
int x[M],y[M];
int n,m;
int find(int x){
	if(p[x]!=x) p[x]=find(p[x]);
	return p[x];
}
void merge(int a,int b){
	if(a==b) return;
	p[a]=b;
	siz[b]+=siz[a];
}
void solve() {
	cin>>n>>m;
	for(int i=1;i<=n;i++) p[i]=i,siz[i]=1;
	for(int i=n+1;i<=2*n;i++) p[i]=i,siz[i]=0;
	map<int,int>mp,mmp;
	for(int i=0;i<m;i++){
		string s;
		cin>>x[i]>>y[i]>>s;
		if(s[0]=='i') mmp[i]=1;
	}
	for(int i=0;i<m;i++){
		int x1=find(x[i]),y1=find(y[i]),x2=find(x[i]+n),y2=find(y[i]+n);
		if(mmp[i]==1){//x1和y1对立
			if(x1==y1||x2==y2){
				cout<<-1<<endl;
				return;
			}
			merge(y2,x1);
			merge(x2,y1);
		}
		else{//x1和y1同阵营
			if(x1==y2||y1==x2){
				cout<<-1<<endl;
				return;
			}
			merge(x1,y1);
			merge(x2,y2);
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		int u=find(i),v=find(i+n);
		if(!mp[u]&&!mp[v]) ans+=max(siz[u],siz[v]),mp[u]=mp[v]=1;
	}
	cout<<ans<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

35.E. Singers’ Tour

n个序号,围成一个圈
构造一个长度为n的数组a,对于每个ai,使得ai加入到第i个序号中,ai+ai加入到第i+1个序号中,ai+ai+ai+加入到第i+2个序号中,这样绕一圈
然后使得每个序号i加入的总数等于bi
无解输出NO

关于数学,把有关的式子都列出来

然后就是解方程,可以利用线性代数的知识(但是忘记了),当然一般的解方程用不到线代

参考CF1618E Singers’ Tour 题解 - 洛谷专栏 (luogu.com.cn)

纠正一下,以上博客 n 2 + n n^2+n n2+n写成 n 2 − n n^2-n n2n

trick:

有关数学,把有关的式子都列出来

需要解方程的话 ,常用的方法是将所有的式子加起来以及第i个式子和第i-1个式子相减

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=4e4+10;
int b[N];
int a[N];
int n;
void solve() {
	cin>>n;
	int sumb=0;
	for(int i=1;i<=n;i++) cin>>b[i],sumb+=b[i];
	if(sumb*2%(n*n+n)){
		cout<<"NO"<<endl;
		return;
	}
	int suma=sumb*2/(n*n+n);
	int sum=0;
	for(int i=2;i<=n;i++){
		if((suma+b[i-1]-b[i])%n){
			cout<<"NO"<<endl;
			return;
		}
		a[i]=(suma+b[i-1]-b[i])/n;
		sum+=a[i];
	}
	a[1]=suma-sum;
	for(int i=1;i<=n;i++){
		if(a[i]<=0){
			cout<<"NO"<<endl;
			return;
		}
	}
	cout<<"YES"<<endl;
	for(int i=1;i<=n;i++) cout<<a[i]<<' ';
	cout<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

36.B. 3-Coloring

交互题
Alice和Bob,我们以Bob的身份下棋
一共有3种颜色,1,2,3
共n*n个网格,Alice会先说出一种颜色,然后我们选择和Aclice的不一样的颜色,选择一个空格放置(也就是说Alice每次会ban掉一种颜色,我们就不能使用那种颜色了)
我们可以做到必胜,如果相邻的格子颜色均不同,那么我们赢

实际上如果颜色没有被ban的话,我们完全可以用两种颜色1和2,交错填

但是颜色会被ban掉,故将3作为备用颜色,先将1和2其中一种颜色填满,比如说1该填的都填满了,那么后面填2和3任意一个都行

trick:

共几种颜色可选,主要颜色可以只用少许,然后剩下的作为备用颜色,当危机时再用,用来容错

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
int n;
void solve() {
	cin>>n;
	int flag=1;
	set<PII>s1,s2;
	for(int i=1;i<=n;i++){
		if(flag){//这一行奇数位置填1,偶数位置填2
			for(int j=1;j<=n;j++){
				if(j%2) s1.insert({i,j});
				else s2.insert({i,j});
			}
		}
		else{//这一行奇数位置填2,偶数位置填1
			for(int j=1;j<=n;j++){
				if(j%2) s2.insert({i,j});
				else s1.insert({i,j});
			}
		}
		flag^=1;
	}
	for(int i=0;i<n*n;i++){
		int c;
		cin>>c;
		if(c!=1){//可以填1
			if(s1.size()){
				auto t=*s1.begin();
				int x=t.first,y=t.second;
				cout<<1<<' '<<x<<' '<<y<<endl;
				cout.flush();
				s1.erase(s1.begin());
			}
			else if(c==2){//1已经填满了,填3到本应该填2的格子
				auto t=*s2.begin();
				int x=t.first,y=t.second;
				cout<<3<<' '<<x<<' '<<y<<endl;
				cout.flush();
				s2.erase(s2.begin());
			}
			else if(c==3){//1已经填满了,填2
				auto t=*s2.begin();
				int x=t.first,y=t.second;
				cout<<2<<' '<<x<<' '<<y<<endl;
				cout.flush();
				s2.erase(s2.begin());
			}
		}
		else if(c!=2){//可以填2
			if(s2.size()){
				auto t=*s2.begin();
				int x=t.first,y=t.second;
				cout<<2<<' '<<x<<' '<<y<<endl;
				cout.flush();
				s2.erase(s2.begin());
			}
			else if(c==1){//2已经填满了,填3到本应该填1的格子
				auto t=*s1.begin();
				int x=t.first,y=t.second;
				cout<<3<<' '<<x<<' '<<y<<endl;
				cout.flush();
				s1.erase(s1.begin());
			}
			else if(c==3){//2已经填满了,填1
				auto t=*s1.begin();
				int x=t.first,y=t.second;
				cout<<1<<' '<<x<<' '<<y<<endl;
				cout.flush();
				s1.erase(s1.begin());
			}
		}
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

37.D1. Domino (easy version)

nm的矩阵,nm为偶数
对于12的小矩阵,可以横着放置,也可以竖着放,共nm/2个
问能否使得k个横着放,剩下的都竖着放,刚好铺满整个矩阵

YES,NO问题,先考虑什么时候是不可能的

横着的放置,必须让这列,剩余的行数为偶数
看行数即n是奇数还是偶数
如果是偶数,那么某相邻两列放置横着的个数必须是偶数,先放两个,仍保持偶数
如果是奇数,那么某相邻两列放置横着的个数必须是奇数,先放1个,变成偶数

如果n是奇数的话,m必为偶数,那么必须两两相邻两列都先得放一个横着的,如果放不满则NO,然后如果剩下的横着的个数为偶数的话,那么YES,否则NO
如果n为偶数,如果m为偶数,如果k为偶数YES,否则NO,如果m为奇数,那么必须先将竖着的放满一列,如果可以并且k为偶数,那么YES,否则NO

这题比较简单

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,m,k;
void solve() {
	cin>>n>>m>>k;
	if(n%2){
		int cnt=m/2;//相邻两列都得放一个横着的
		if(k<cnt){
			cout<<"NO"<<endl;
			return;
		}
		cnt-=k;
		if(cnt%2){
			cout<<"NO"<<endl;
			return;
		}
		cout<<"YES"<<endl;
		return;
	}
	else{
		if(m%2==0){
			if(k%2==0) cout<<"YES"<<endl;
			else cout<<"NO"<<endl;
			return;
		}
		int kk=n*m/2-k;//竖着的个数
		if(kk<n/2){
			cout<<"NO"<<endl;
			return;
		}
		if(k%2==0) cout<<"YES"<<endl;
		else cout<<"NO"<<endl;
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

38.D1. Chopping Carrots (Easy Version)

长度为n的数组a(数[1,3000])非降序
构造一个长度为n的数组p(无需真的构造出来),它的代价是ai/pi(下取整)的最大值减去ai/pi(下取整)的最小值
pi小于等于k
问代价最小是多少

最好平均
让大数除以大数,让小数除以小数
数据比较小,考虑暴力
对于ai,e[i]存储ai除以k到1,然后升序(不用,肯定是升序的)
pi就在e[i]里面选
如果p1确定了的话,那么我们记录最小值,后面每次选大于等于最小值最小的那个数,这样是在p1确定的情况下的最优解
所以枚举e[1]中的数作为p1,然后每种情况都算成该情况下的最优解,然后综合取min

但这样不是很对,因为确定了p1,然后后面不能只选大于等于最小值的,因为最小值没有确定,可能选更小的更优

问题在于最小值没有确定,所以我们直接确定最小值([a[1]/k,a[1]],不能只选a[1]除以1,除以2,…除以k,因为这样不连续,中间也有可能取到),然后后面选大于等于最小值的最小的数(包括p[1]也要这样选)

最后挂一下惊人的战绩

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

trick:

1.呜呜呜,再也不用#define int long long了,导致莫名其妙的memory limit

2.如何想到确定最小值,因为前面想到确定p[1],但是问题出现了,如果最小值没有确定,那么后面就不能二分选大于等于最小值的第一个数,为解决该问题,故思路改成确定最小值

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=3010;
vector<int>e[N];
int a[N];
int n,k;
void solve() {
	cin>>n>>k;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) e[i].clear();
	for(int i=1;i<=n;i++){
		for(int j=k;j>=1;j--){
			e[i].push_back(a[i]/j);
		}
	}
	int ans=2e9;
	for(int mn=a[1]/k;mn<=a[1];mn++){//枚举最小值
		int maxn=0;
		bool ok=true;
		for(int j=1;j<=n;j++){
			int l=0,r=(int)e[j].size()-1;
			while(l<r){
				int mid=(l+r)/2;
				if(e[j][mid]>=mn) r=mid;
				else l=mid+1;
			}
			if(e[j][l]>=mn) maxn=max(maxn,e[j][l]);
			else{
				ok=false;
				break;
			}
		}
		if(ok) ans=min(ans,maxn-mn);
	}
	cout<<ans<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
	cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

39.B. Doremy’s Connecting Plan

一共有n个城市
第i个城市有ai个人

添加无向边i,j(如果添上去之后,整个连通块的总人数大于等于ijc
问能否使得整个图形为一个连通块
YES,NO

参考CF1889B 题解 - 洛谷专栏 (luogu.com.cn)

CF1889B 题解 - 洛谷专栏 (luogu.com.cn)

trick:

1.关于数学,题目中出现数学式子就认定为有关数学,那么把相关的式子都列出来

2.猜想一个结论,证明的话,可以反证,用数学式子表示出来,得出矛盾

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int n,c;
struct node{
	int a;
	int idx;
	int value;
	bool operator<(const node &W)const{
		return value<W.value;
	}
}q[N];
void solve() {
	cin>>n>>c;
	for(int i=1;i<=n;i++) cin>>q[i].a,q[i].idx=i,q[i].value=i*c-q[i].a;
	sort(q+2,q+2+n-1);
	int sum=q[1].a;//1所在的连通块的总和
	for(int i=2;i<=n;i++){
		if(sum<q[i].value){
			cout<<"NO"<<endl;
			return;
		}
		sum+=q[i].a;
	}
	cout<<"YES"<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

40.B. Different Rules

共n名参赛者,两轮比赛
得分为第一轮比赛的名次+第二轮比赛的名次,分数越低,名次越高
尼古拉作为其中一名参赛者,第一轮第x名,第二轮第y名
问他能获得的最低名次和最高名次

尼古拉的第一轮名次x确定,那么其他人的名次按顺序排就行了,分别为1到x-1,x+1到n
同理,第二轮其它人名次为1到y-1,y+1到n,但是顺序不确定

要最差的名次,那么肯定要使得越多人分数小于等于x+y,那么对于枚举第一轮的名次,在第二轮里二分,找到最大的,使得两者之和小于等于x+y,如果找不到,就浪费掉最大的
要最好的名次,那么肯定要使得越多人分数大于x+y,那么 枚举第一轮的名次,在第二轮里二分,找到最小的,使得两者之和大于x+y,如果找不到,那么就浪费掉最小的

但是,没注意数据范围,n达1e9,单单枚举就超时了
数据大到可怕,猜测这题为找规律

参考cf–1313–B. Different Rules_公13的 cfmf.b.d.5 _j u…, . ,.x%)-, c-CSDN博客

trick:

当枚举n都不行时,肯定是找规律,当输入不同时,结果也不同,所以规律肯定是和输入变量x(可能不止一个变量)有关的式子

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,x,y;
void solve() {
	cin>>n>>x>>y;
	int ans2=min(x+y-1,n);
	int ans1;
	if(x+y<n+1) ans1=1;
	else ans1=min(x+y-n+1,n);
	cout<<ans1<<' '<<ans2<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

41.F. Ira and Flamenco

只有连续m个数才能满足要求

从小到大排序并去重,放入a中, a i a_i ai表示去重后第i小的元素

统计每个元素出现的次数, b i b_i bi表示 a i a_i ai出现的次数

a i − m + 1 a_{i-m+1} aim+1= a i a_i ai-m+1时,区间[i-m+1,i]满足条件

维护b[i]的前缀积

trick:

常用的技巧:

从小到大排序并去重,放入a中, a i a_i ai表示去重后第i小的元素

统计每个元素出现的次数, b i b_i bi表示 a i a_i ai出现的次数

	for(int i=0;i<n;i++){
		int x;
		cin>>x;
		mp[x]++;
	}
	int cnt=0;
	for(auto [x,y]:mp){
		a[++cnt]=x;
		b[cnt]=y;
	}
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10,mod=1e9+7;
int a[N],b[N];
int mul[N];
int n,m;
int qmi(int a,int b){
	int res=1;
	while(b){
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}
void solve() {
	cin>>n>>m;
	map<int,int>mp;
	for(int i=0;i<n;i++){
		int x;
		cin>>x;
		mp[x]++;
	}
	int cnt=0;
	for(auto [x,y]:mp){
		a[++cnt]=x;
		b[cnt]=y;
	}
	mul[0]=1;
	for(int i=1;i<=cnt;i++) mul[i]=mul[i-1]*b[i]%mod;
	int ans=0;
	for(int i=m;i<=cnt;i++){
		if(a[i-m+1]==a[i]-m+1) ans=(ans+mul[i]*qmi(mul[i-m],mod-2)%mod)%mod;
	}
	cout<<ans<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

42.B1. Candy Party (Easy Version)

Codeforces Round 896 (Div. 2)_codeforces896div2-CSDN博客

trick:

任何一个数分解成两个2的幂次的形式是唯一的

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N];
int n;
bool check(int n){
	if(n>0&&(n&(n-1))==0) return true;
	return false;
}
int lowbit(int x){
	return x & -x;
}
void solve() {
	cin>>n;
	int sum=0;
	for(int i=1;i<=n;i++) cin>>a[i],sum+=a[i];
	vector<int>bit(31,0);
	if(sum%n){
		cout<<"NO"<<endl;
		return;
	}
	int d=sum/n;
	for(int i=1;i<=n;i++){
		if(a[i]==d) continue;
		int diff=abs(a[i]-d);
		int p=lowbit(diff);//小
		int q=p+diff;//大
		if(!check(q)){
			cout<<"NO"<<endl;
			return;
		}
		if(a[i]<d){
			bit[log2(q)]++;
			bit[log2(p)]--;
		}
		else{
			bit[log2(q)]--;
			bit[log2(p)]++;
		}
	}
	for(int i=0;i<31;i++){
		if(bit[i]){
			cout<<"NO"<<endl;
			return;
		}
	}
	cout<<"YES"<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

43.D. Matrix Cascade

有n个长度为n的01串
操作:对于(i,j),对于x-i大于等于abs(y-j)的,全部0和1反转
问将所有元素变成0的最少操作次数,一定有解

参考D. Matrix Cascade (element-ui.cn)

递推

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=3e3+10;
int sum[N][N],l[N][N],r[N][N];
char s[N][N];
int n;
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) cin>>(s[i]+1);
	int ans=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			int tmp=sum[i-1][j]+l[i-1][j-1]+r[i-1][j+1];
			if(tmp%2){
				if(s[i][j]=='1') s[i][j]='0';
				else s[i][j]='1';
			}
			if(s[i][j]=='1'){
				ans++;
				sum[i][j]=tmp+1;
				l[i][j]=l[i-1][j-1]+1;
				r[i][j]=r[i-1][j+1]+1;
			}
			else{
				sum[i][j]=tmp;
				l[i][j]=l[i-1][j-1];
				r[i][j]=r[i-1][j+1];
			}
		}
	}
	cout<<ans<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

44.F. Asya And Kittens

一共有n只小猫,然后相邻的两只小猫中间都有隔板

第i天将索引为x的小猫和索引为y的小猫之间的隔板拿掉(x所在的猫群和y所在的猫群相邻)
问最初小猫的排列方式,一定有解

参考题解 CF1131F 【Asya And Kittens】 - 洛谷专栏 (luogu.com.cn)

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=15e4+10;
int p[N];
int n;	
map<int,vector<int>>s;
int find(int x){
	if(p[x]!=x) return p[x]=find(p[x]);
	return p[x];
}
void merge(int x,int y){
	int a=find(x),b=find(y);
	if(s[a].size()<s[b].size()) swap(a,b);
	for(int i=0;i<(int)s[b].size();i++) s[a].push_back(s[b][i]);
	p[b]=a;
}
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++){
		p[i]=i;
		s[i].push_back(i);
	}
	for(int i=0;i<n-1;i++){
		int x,y;
		cin>>x>>y;
		merge(x,y);
	}
	int root=find(1);
	for(int i=0;i<(int)s[root].size();i++) cout<<s[root][i]<<' ';
	cout<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

45.D. Relatively Prime Graph

n个顶点
构造m条边,使得任意一条边,两个顶点最大公因数为1
无解输出Impossible
否则输出Possible,并输出m条边

要求图连通,无自环,无重边

直接暴力枚举,虽然是 n 2 n^2 n2,但是由于需要产生的边最多1e5,然后对于顶点i,只有枚举到它的倍数才不连,其它均连,很快就就能产生m条边

trick:

1.两个数的最大公因数为1,不只有两个数互质,还可以其中一个数为1

2.可能枚举时间复杂度为O( n 2 n^2 n2),但是需要产生的边最多1e5,产生的很快,很快break,就不会超时

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
int n,m;
int gcd(int a,int b){
	if(b==0) return a;
	return gcd(b,a%b);
}
void solve() {
	cin>>n>>m;
	vector<PII>ans;
	if(m<n-1||m>n*(n-1)/2){
		cout<<"Impossible"<<endl;
		return;
	}
	int cnt=0;
	bool ok=true;
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++){
			if(gcd(i,j)==1) ans.push_back({i,j}),cnt++;
			if(cnt==m){
				ok=false;
				break;
			}
		}
		if(!ok) break;
	}
	if(cnt!=m){
		cout<<"Impossible"<<endl;
		return;
	}
	cout<<"Possible"<<endl;
	for(auto v:ans) cout<<v.first<<' '<<v.second<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

46.Problem - 584C - Codeforces

长度为n的字符数s1和s2,均由小写字母组成
构造一个字符串s3,使得s3和s1刚好有t个字符不同,并且s3和s2刚好有t个字符不同
无解输出-1

比较两个字符串,记录字符不相等的位置以及个数cnt
我们不要凭空构造,我们在s1的基础上改造,就是修改s1的t个字符
记录s1和s2有几个字符不相等,记为cnt
如果cnt小于t,那么把这cnt个字符改成和s1,s2均不一样的,然后补足,对于s1和s2相等的字符,修改,直到个数等于cnt
如果cnt等于t,那么直接把这cnt个字符修改成和s1,s2均不一样的
如果cnt大于t,如果cnt大于2*t,则无解,否则,将前cnt-t个字符修改成和s2一样的,t个字符中剩下的修改成和s1,s2均不一样的

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,t;
string s1,s2;
void solve() {
	cin>>n>>t;
	cin>>s1>>s2;
	int cnt=0;
	vector<int>pos;
	map<int,int>mp;
	for(int i=0;i<n;i++){
		if(s1[i]!=s2[i]){
			pos.push_back(i);
			cnt++;
		}
		else mp[i]=1;
	}
	string tmp=s1;
	if(cnt<=t){
		for(int i=0;i<cnt;i++){
			for(char ch='a';ch<='z';ch++){
				if(ch!=s1[pos[i]]&&ch!=s2[pos[i]]){
					tmp[pos[i]]=ch;
					break;
				}
			}
		}
		if(cnt==t){
			cout<<tmp<<endl;
			return;
		}
		for(auto v:mp){
			for(char ch='a';ch<='z';ch++){
				if(ch!=s1[v.first]){
					tmp[v.first]=ch;
					break;
				}
			}
			cnt++;
			if(cnt==t){
				cout<<tmp<<endl;
				return;
			}
		}
	}
	else {
		if(cnt>2*t){
			cout<<-1<<endl;
			return;
		}
		for(int i=0;i<cnt-t;i++) tmp[pos[i]]=s2[pos[i]];
		for(int i=cnt-t;i<t;i++){
			for(char ch='a';ch<='z';ch++){
				if(ch!=s1[pos[i]]&&ch!=s2[pos[i]]){
					tmp[pos[i]]=ch;
					break;
				}
			}
		}
		cout<<tmp<<endl;
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

47.C1. Balanced Removals (Easier)

三维空间一共有n个不同的点(n为偶数)
操作:删掉两个点(两个点之间的连线不能有其它点)
操作n/2次,要求删除所有点,一定有解

排个序,按照优先x,再y,最后z,升序,这样不行,试了(0,1,1),(1,0,1)和(1,1,1)
数据比较小,考虑暴力
对于某个点,要想它和另一个点之间没有其它的点,那么只要找和它距离最近的点就行

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2010;
struct node{
	double x,y,z;
}q[N];
double sqr(int x){
	return x*x;
}
double d(int x1,int y1,int z1,int x2,int y2,int z2){
	return sqrt(sqr(x1-x2)+sqr(y1-y2)+sqr(z1-z2));
}
int n;
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) cin>>q[i].x>>q[i].y>>q[i].z;
	map<int,int>mp;//标记是否删除
	for(int i=1;i<=n;i++){
		if(mp[i]) continue;
		double dist=2e9;
		int pos=-1;
		for(int j=1;j<=n;j++){
			if(i==j||mp[j]) continue;
			double dd=d(q[i].x,q[i].y,q[i].z,q[j].x,q[j].y,q[j].z);
			if(dd<dist){
				dist=dd;
				pos=j;
			}
		}
		mp[i]=mp[pos]=1;
		cout<<i<<' '<<pos<<endl;
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

48.D. Cyclic Rotation

长度为n的数组a和b(数组b是数组a的排列)
操作:选择两个相等的数的索引l和r,将[l,r]循环右移,就是把a[l]移到a[r]的后面
问能否将数组a变成数组b,YES,NO

操作性问题,操作对象即为后面有和它相等的元素,专门研究这样的一个元素的移动情况

YES,NO,先考虑NO
NO的情况就是只能往后移,不能往前移

对于结果串,如果两个相邻字符相等,那么其中一个字符可以移到前面任何一个位置,只要记住前面应该有哪些字符以及字符的个数,然后check前面是否一一对应

trick:

将字符串变成给定串,结果已知,可以逆推

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N],b[N];
int n;
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) cin>>b[i];
	map<int,int>mp;
	for(int i=n,j=n;i>=1;i--){
		while(j>1&&b[j]==b[j-1]) mp[b[j]]++,j--;
		if(j>=1&&a[i]==b[j]) j--;
		else if(mp[a[i]]) mp[a[i]]--;
		else {
			cout<<"NO"<<endl;
			return;
		}
	}
	cout<<"YES"<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

49.B. Fix a Tree

n个节点的树
a[i]表示编号为i的节点的父节点(树的话是没有方向的,所以直接相连即可)
给定数组不一定有效,问最少修改几个元素使得有效(仅有一个根的树)

直接并查集连成连通块
找到一个根,然后合并,如果成环就不合并,就指向根
如果找不到根,那么肯定成环了 ,那么就找到成环的那个点,作为根

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N];
int p[N];
int p1[N];
int n;
int find(int x){
	if(p[x]!=x) p[x]=find(p[x]);
	return p[x];
}
int find1(int x){
	if(p1[x]!=x) p1[x]=find1(p1[x]);
	return p1[x];
}
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) p[i]=i,p1[i]=i;
	for(int i=1;i<=n;i++) cin>>a[i];
	int root=-1;
	for(int i=1;i<=n;i++){
		if(a[i]==i){
			root=i;
			break;
		}
	}
	if(root==-1){//一定有环
		for(int i=1;i<=n;i++){
			int u=find1(i),v=find1(a[i]);
			if(u!=v) p1[u]=v;
			else{//成环了
				root=i;
				break;
			}
		}
		int ans=0;
		a[root]=root;
		for(int i=1;i<=n;i++){
			if(a[i]==i) a[i]=root,ans++;
			else{
				int u=find(i);
				int v=find(a[i]);
				if(u==v){
					a[i]=root;
					ans++;
				}
				else p[u]=v;
			}
		}
		cout<<ans<<endl;
		for(int i=1;i<=n;i++) cout<<a[i]<<' ';
		cout<<endl;
		return;
	}
	int ans=-1;
	for(int i=1;i<=n;i++){
		if(a[i]==i) a[i]=root,ans++;
		else{
			int u=find(i);
			int v=find(a[i]);
			if(u==v){
				a[i]=root;
				ans++;
			}
			else p[u]=v;
		}
	}
	cout<<ans<<endl;
	for(int i=1;i<=n;i++) cout<<a[i]<<' ';
	cout<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

50.B. Aroma’s Search

二维平面上有无数个数据节点
第0个数据节点坐标为(x0,y0),第i个数据节点由第i-1个数据节点决定,可以算出

给定初始坐标(xs,ys),然后可以上下左右移动,每次移动花费1秒,当经过数据节点时,就可以收集数据节点,问t秒最多收集几个数据节点
数据节点按编号递增是有规律的,是疯狂递增的
贪心
先从起点到数据节点那条折线上的一个点(找距离最近的,这里错了,不一定是距离最近的,应该要枚举每个点),然后再往下看,看最多能收集几个数据节点,下面的点密集,然后如果都收集完了,再往上
可以对各个数据节点前缀和
前缀和不可行,因为数达1e16,相加会超过long long ,而且实际上用不着前缀和

参考题解 CF1292B 【Aroma’s Search】 - 洛谷专栏 (luogu.com.cn)

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N = 110;
int x[N], y[N];
int ax, ay, bx, by;
int xs, ys;
int t;
int n;
int ans;
void solve() {
	cin >> x[0] >> y[0] >> ax >> ay >> bx >> by;
	cin >> xs >> ys >> t;
	for (int i = 1;; i++) {
		x[i] = x[i - 1] * ax + bx;
		y[i] = y[i - 1] * ay + by;
		if (x[i] > 2e16 || y[i] > 2e16) {
			n = i - 1;
			break;
		}
	}
//	for (int i = 0; i <= n; i++) {
//		cout << i << ' ' << x[i] << ' ' << y[i] << endl;
//	}
	int res=0;
	int tt=t;
	for (int mini = 0; mini <= n; mini++) {
		t=tt;
		int d=abs(x[mini]-xs)+abs(y[mini]-ys);
		t-=d;
		if(t<0) continue;
		ans=1;
		for(int i=mini-1;i>=0;i--){
			t-=(abs(x[i+1]-x[i])+abs(y[i+1]-y[i]));
			if(t<0) break;
			ans++;
		}
		t-=abs(x[mini]-x[0])+abs(y[mini]-y[0]);
		if(t<0){
			res=max(res,ans);
			continue;
		}
		for(int i=mini+1;i<=n;i++){
			t-=(abs(x[i]-x[i-1])+abs(y[i]-y[i-1]));
			if(t<0) break;
			ans++;
		}
		res=max(res,ans);
	}
	cout<<res<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
//    cin>>t;
	while (t--) {
		solve();
	}
	return 0;
}
  • 10
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值