codeforces 1400分

文章目录

1.B. Phoenix and Beauty

两个整数n和k(有一个坑点是它括号里写了n,k的大小顺序,然后把k放前面,然后就以为先输入k)
长度为n的序列
美丽序列:所有长度为k的连续子序列和相同
插入若干整数(1到n)使得序列变成美丽序列,无解输出-1

数据比较小,直接暴力枚举
题目中说如果存在的话,长度不超过1e4,那么肯定是通过暴力不断插入

有一种通用的万能的构造方法,就是一直在每组k的后面插入,然后维护前面的全满足

比如 1 2 3 4 ,k=3
那么要满足第二组k个,那么就要在3后面插个1,同理,依次需要按顺序插入2,3,4然后如果要插入的数就是待插的数那么很好,如果没有就要自己插入新的数,直到最后一个待插入的数插进去,题目中说了如果存在的话,那么长度不超过1e4,那么当长度超过1e4时若还没有插完,那么无解
事实证明这种构造方法并不对,因为如果待插入的数在前面没出现过的话,那么永远也插不进去

可以发现,如果k为4的话,那么必须刚好4种数字,然后一直循环,这样才能满足题意,如果数字种数超了k个,那么无解,否则可以通过补数和循环,循环次数的话可以用10000/数字种数,因为题目数据保证了长度最大10000,所以可以直接让长度最大,那么肯定保证循环次数足够多

坑点:括号里写了n,k的大小顺序,然后把k放前面,然后就以为先输入k

trick:

1.自以为想出了一种万能的通用的构造方法,实际是错的,避免方法是先通过样例检验,然后自己再造一些极端的数据来验证

2.如果一个数组中所有长度为 k的子数组的和都相同,那么这个数组就是美丽的。数组的子数组是任何连续元素的序列----这是美丽数组的性质,我们可以通过手玩举一些例子来挖掘其背后的性质:数字种数必须小于等于k,然后不断循环,这样才能满足性质,这一性质的挖掘是这题解题的关键

3.题目说了构造长度不超过10000,那么我们不用考虑多长,就直接长度最大化,接近10000,这样就不用考虑要多长了

4.数据比较小,暴力是一个很好的方向

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=110;
int a[N];
int n,k;
void solve() {
   
	cin>>n>>k;
	set<int>s;
	map<int,int>mp;
	for(int i=1;i<=n;i++) cin>>a[i],s.insert(a[i]),mp[a[i]]++;
	int maxn=0;
	for(auto v:mp){
   
		maxn=max(maxn,v.second);
	}
	if((int)s.size()>k){
   
		cout<<-1<<endl;
		return;
	}
	int len=s.size();
	vector<int>ans;
	while(s.size()){
   
		ans.push_back(*s.begin());
		s.erase(s.begin());
	}
	for(int i=0;i<k-len;i++) ans.push_back(1);
	cout<<10000/(int)ans.size()*(int)ans.size()<<endl;
	for(int i=1;i<=10000/(int)ans.size();i++){
   
		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;
}

2.C. Rotation Matching

长度为n的a序列和b序列是全排列
操作:将序列循环右移或左移
操作不限次数

求最大匹配元素对数量
匹配:大小和位置均相同

只要移动一个序列即可

最—贪心,一边遍历,一边贪,不可行
猜一下:让第一个数匹配,然后数匹配总数,不可行,因为可能虽然第一个不匹配,但是其它匹配了很多
挖掘操作背后的性质,什么是一定的,什么是不变的—想不到,感觉就算想挖掘也想不出,这作为其中一个思考的方向,当然想不出也没办法,也很正常,因为一个题目在被出出来的时候,基本解题方向已经被限死了,很小范围,所以这个方向想不出是很正常的,作为一个可能的方向,想不出就换方向

统计每个b i需要往右移多少位和a j 对齐(b i ==a j )然 后 看 看 往 右 移 动 几 次 对 答 案 最 多

trick:

1.反向思考,先按照正常的朴素的正向思维想(往往不饶弯子),比如这题就是每循环右移一次(0到n次),求出匹配的数量,然后取最大匹配数量即可,但是这样肯定超时了,所以反过来思考(转一次弯),将每个字符匹配累加到循环右移的次数上,这样利用一个map,就不会超时了,遍历b的每个数,将每个数移到正确位置的次数++,比如将3移到正确的位置需要两次,将4移到正确的位置也需要两次,那么移动两次匹配数量则为2

2.挖掘操作背后的性质,什么是一定的,什么是不变的—想不到,感觉就算想挖掘也想不出,这作为其中一个思考的方向,当然想不出也没办法,也很正常,因为一个题目在被出出来的时候,基本解题方向已经被限死了,很小范围,所以这个方向想不出是很正常的,作为一个可能的方向,想不出就换方向

#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;
	map<int,int>mp,pos;
	for(int i=1;i<=n;i++) cin>>a[i],pos[a[i]]=i;
	for(int i=1;i<=n;i++) cin>>b[i];
	for(int i=1;i<=n;i++){
   
		if(pos[b[i]]>=i) mp[pos[b[i]]-i]++;
		else{
   
			mp[pos[b[i]]+n-i]++;
		}
	}
	int ans=0;
	for(int i=0;i<=n;i++){
   
		ans=max(ans,mp[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;
}

3.C. Element Extermination

长度为n的数组a,最初为全排列
操作:可以移除相邻的正序的两个数的其中一个
次数不限
问能否让数组长度变成1

完全升序的肯定可以
降升也可以,两降就不行

逆序对的数量(包括相等的对数)如果大于等于2,那么就NO,否则YES
坑点:有的题目YES,NO大小写不区分,有的题目区分大小写
逆序对用归并排序
逆序对错了,想出一个思路一定得用样例仔细验证,不要看一眼感觉对,代码都写出来了才发现思路错了
不要通过样例来猜做法,首先样例并不全,其次样例具有误导性,还是应该通过题目给的信息来自己手玩造样例来推测(要造那种复杂的,然后不同种类的多一些,全一些),题目所给样例只是用来验证思路的正确性的

通过手玩造复杂样例可以发现,只要第一个元素小于最后一个元素,那么YES,否则NO

坑点:

1.有的题目YES,NO大小写不区分,有的题目区分大小写

2.想出一个思路一定得用样例仔细验证,不要看一眼感觉对,代码都写出来了才发现思路错了、

trick:

手玩造复杂,不同种类的样例

不要通过样例来猜做法,首先样例并不全,其次样例具有误导性,还是应该通过题目给的信息来自己手玩造样例来推测(要造那种复杂的,然后不同种类的多一些,全一些),题目所给样例只是用来验证思路的正确性的

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=3e5+10;
int a[N];
int n;
void solve() {
   
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	if(a[1]<a[n]) 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;
}

4.D. Epic Transformation

长度为n的数组a
操作:删除两个不同的元素
输出数组最少剩下几个元素

与顺序无关 ,先排个序
成对删除,如果是奇数个,至少剩一个
先把数量最多的消耗,不然它自己是消耗不了的
通过手玩发现,如果最高的那个数量大于等于其它所有之和,那么答案即为最高的减去其它之和
否则,一定可以消完,当然偶数个消成0,奇数个消成1

trick:手玩

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N];
int n;
void solve() {
   
	cin>>n;
	map<int,int>mp;
	int sum=0;
	for(int i=1;i<=n;i++) cin>>a[i],mp[a[i]]++;
	int maxn=0;
	for(auto v:mp){
   
		maxn=max(maxn,v.second);
		sum+=v.second;
	}
	if(maxn>=sum-maxn) cout<<maxn-(sum-maxn)<<endl;
	else{
   
		if(n%2==1) cout<<1<<endl;
		else cout<<0<<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.C. Team

一共有n张数字0,m张数字1
构造序列,使得不能有两张0相邻,不能有连续3个1

一个0配一个1或者两个1

一个0最多配两个1,然后最多最后再来两个1
一个0最少配一个1

trick:这边主要用到平均分的技巧(上次已经总结过了)

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,m;
void solve() {
   
	cin>>n>>m;
	if(n>m+1||2*n+2<m){
   
		cout<<-1<<endl;
		return;
	}
	//特判
	if(2*n+2==m){
   
		cout<<"11";
		for(int i=0;i<n;i++) cout<<"011";
		cout<<endl;
		return;
	}
	if(2*n+1==m){
   
		cout<<"1";
		for(int i=0;i<n;i++) cout<<"011";
		cout<<endl;
		return;
	}
	if(n==m+1){
   
		cout<<"0";
		for(int i=0;i<n-1;i++) cout<<"10";
		cout<<endl;
		return;
	}
	//平均分
	int res=m-n;//一个0配一个1之后还剩几个1
	for(int i=0;i<res;i++) cout<<"011";
	for(int i=0;i<n-res;i++) cout<<"01";
	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;
}

6.B. Find The Array

长度为n的数组a
beautiful:任意相邻两个数,其中一个数是另一个数的因数,ai和bi的差的绝对值的和的两倍小于等于S
构造美丽数组(答案总是存在)–>万能通用的构造方法

与顺序无关,排个序
b1先等于a1,然后依次遍历,如果ai-1是bi-1的倍数,那么就可以,否则,bi就延续bi-1,但其实并不科学,不能证明,甚至自己觉得可以构造出反例

让数组b的奇数位都等于1,偶数位和数组a的偶数位对应相等或者让数组b的偶数位都等于1,奇数位和数组a的奇数位对应相等

这样的话,奇数位相差都为0或者偶数位相差都为0,其中有一种情况能满足差的绝对值的和的两倍小于等于S,只要分别构造出来,检验即可

trick:

1.两倍可以往奇偶位的方向想

2.相邻两个数满足一个数能被另一个数整除,比较特殊万能的就是1,因为1能够整除任意一个数

3.如果有一个思路,那么需要去验证正确性,如果代码比较容易写的话, 那么直接写代码验证,如果代码不容易写,那么就通过构造例子,看能否举出反例,这是策略

4.如果构造序列需要判断一些条件,比较麻烦,可以直接将可能的序列构造出来 ,检验即可

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=55;
int a[N],b[N];
int n;
void solve() {
   
	cin>>n;
	int sum=0;
	for(int i=1;i<=n;i++) cin>>a[i],sum+=a[i];
	for(int i=1;i<=n;i++){
   
		if(i%2==1) b[i]=1;
		else b[i]=a[i];
	}
	int res=0;
	for(int i=1;i<=n;i++){
   
		res+=abs(a[i]-b[i]);
	}
	if(res*2<=sum){
   
		for(int i=1;i<=n;i++) cout<<b[i]<<' ';
		cout<<endl;
		return;
	}
	for(int i=1;i<=n;i++){
   
		if(i%2==1) b[i]=a[i];
		else b[i]=1;
    }
	for(int i=1;i<=n;i++) cout<<b[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;
}

7.B. Quasi Binary

将正整数n表示成准二进制数之和
要求准二进制数的个数最少
准二进制数:只包含0和1的十进制数

因为最小可以为1(0用不着),所以所有数都是可得的
两位数的话,最大的准二进制数可以为11,最小为10
三位数的话,最小的准二进制数为100,最大的准二进制数为111

贪心

从大的开始贪

从高位到低位,该位为x,如果x小于等于1,那么就等于x,如果x大于1,那么后面所有位都变成1

这样贪是错的,因为这样可能使得1的个数过多

我们一位一位看,对于某一位来说,只有可能是0和1,然后我们就分别凑每一位就行了,比如说某一位是x,那么该位就需要x个1,最少个数即所有位上的数的最大值

那么如何构造呢?例如523,从最低位开始遍历,将3平均分到前3个数中,将20平均分到前2个数中,将500平均分到前5个数中

trick:

1.如果思考方向为贪心的话,尤其要验证思路,通过造极端复杂的样例,看思路是否正确

2.对于凑十进制数(只能存在1和0,主要是1),一个思考方向是拆位,分别凑每一位,方法是每一位上的数x平均放到前x个数中(要乘以幂)

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=110;
int a[N];
int n;
void solve() {
   
	cin>>n;
	string s=to_string(n);
	int len=s.size();
	int cnt=0;
	for(int i=0;i<len;i++) cnt=max(cnt,(int)(s[i]-'0'));
	for(int i=len-1,mi=0;i>=0;mi++,i--){
   
		int digit=s[i]-'0';
		for(int j=0;j<digit;j++){
   
			a[j]+=pow(10,mi);
		}
	}
	cout<<cnt<<endl;
	for(int i=0;i<cnt;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;
}

8.C. Heavy Intervals

一共有n个区间,第i个区间的单位长度权重为ci
操作:重新排列l,r,c
求所有区间权重之和最小是多少

可以得到所有区间长度和是一定的,然后n个区间的单位长度权重也是一定的,所以就是对一整段长度一定的区间进行分割,给予其不同的单位权重,使得总权重最小,那么就要让平均权重最小,将更小的单位权重给更长的区间,这样可以让平均权值最小

所以就是每次选取当前最短的区间,做法是先将l升序,将r放在set中,对于最大的l,在set中进行二分找到大于它的第一个,构成的区间即为最短的区间,然后在set中删除该元素

区间长度升序,然后权重降序,相乘并相加

trick:
1.找到什么是一定的,这个很重要,往往是解题的关键,比如区间总长度一定,要使得总权重最小,那么就要让平均权重最小

2.在set中进行二分,s.upper_bound(x)表示set中大于x的第一个元素,返回其迭代器

3.set删除元素

erase(iterator) ,删除定位器iterator指向的值

erase(key_value),删除键值key_value的值

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int l[N];
int c[N];
int n;
void solve() {
   
	cin>>n;
	for(int i=0;i<n;i++) cin>>l[i];
	set<int>s;
	for(int i=0;i<n;i++) {
   
		int x;
		cin>>x;
		s.insert(x);
	}
	for(int i=0;i<n;i++) cin>>c[i];
	sort(l,l+n);
	sort(c,c+n);
	reverse(c,c+n);
	vector<int>ans;
	for(int i=n-1;i>=0;i--){
   
		auto it=s.upper_bound(l[i]);//在set中找到大于l[i]的第一个元素,返回其迭代器
		int r=*it;
		ans.push_back(r-l[i]);
		s.erase(it);
	}
	sort(ans.begin(),ans.end());
	int res=0;
	for(int i=0;i<n;i++){
   
		res+=c[i]*ans[i];
	}
	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;
}

9.D. Game With Array

P构造和为S的N个正整数,选择一个在[0,S]的一个元素k
V在以上数组中选择若干元素之和等于K才能赢
问P能否获胜

坑点:YES,NO

正整数,最小为1
手玩
N为1肯定YES
N小于等于S则YES,构造方法即为平均分,1是没有出现的
2 3
1 2

2 4
2 2

3 4
1 1 2

3 5
1 2 2

3 6

2 2 2

trick:

1.手玩找出的规律

2.平均分这一技巧是之前积累过的,为这题构造提供了一个好方法

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,s;
void solve() {
   
	cin>>n>>s;
	if(n==1){
   
		if(s==1) cout<<"NO"<<endl;
		else{
   
			cout<<"YES"<<endl;
			cout<<s<<endl;
			cout<<s-1<<endl;
		}
		return;
	}
	if(2*n<=s){
   
		cout<<"YES"<<endl;
		int ave=s/n;//平均分
		int res=s-ave*n;//剩下的
		vector<int>ans;
		for(int i=0;i<n;i++) ans.push_back(ave);
		for(int i=0;i<res;i++) ans[i]++;
		for(auto v:ans) cout<<v<<' ';
		cout<<endl;
		cout<<1<<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;
}

10.B. Composite Coloring

长度为n的数组a,均为合数
选择小于等于11的一个整数m
1到m共m中颜色,给所有元素进行着色
要求:1到m都要被用到,所有元素都要有颜色,颜色相同的两个元素不能互质
题目一定有解

数据比较小,可以考虑暴力,直接对于每种颜色,去遍历其它数,如果不互质,那么就涂相同的颜色,这样不行,因为相同颜色的必须全部都不互质
如果n小于等于11的话,那么直接n种颜色全不同即可
可以简化一下,全部偶数都取同一种颜色,然后只要看奇数,通过手玩,由于均为合数,所以数比较少,然后由于合数可以分解成若干个质数的乘积,发现从3开始,5,7,11,13,17,19,23,29,31,一直到小于1000,用个质数筛就行了

坑点:读题还是看英文,当看不懂的时候再看一眼翻译,因为有些词翻译并不准确

trick:

1.合数可以分解成若干个质数的乘积(唯一),之前积累过的,在这里又用到了

2.分奇偶是一个很好的方向,如何去想呢?只要是有关数学的,比如因数,质数,合数

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1010;
int a[N];
int color[N];
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>>n;
	memset(color,0,sizeof color);
	for(int i=1;i<=n;i++) cin>>a[i];
	int ans=0;
	map<int,int>mp;
	for(int i=1;i<=n;i++){
   
		for(int j=0;j<cnt;j++){
   
			if(a[i]%prime[j]==0){
   
//				cout<<i<<' '<<prime[j]<<endl;
				if(mp[prime[j]]==0){
   
					ans++;
					color[i]=ans;
					mp[prime[j]]=ans;
				}
				else color[i]=mp[prime[j]];
				break;
			}
		}
	}
	cout<<ans<<endl;
	for(int i=1;i<=n;i++){
   
		cout<<color[i]<<' ';
	}
	cout<<endl;
}
signed main() {
   
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
	get_prime(1000);
    cin>>t;
	while(t--) {
   
		solve();
	}
	return 0;
}

11.B. Applejack and Storages

初始共n个木板,长度为ai
共q个订单,+x代表收到长度为x的木板,-x代表取出长度为x的木板

问每次订单之后能否建造两个所需形状的仓库(两个正方形或者一个正方形一个长方形)

能否建造正方形取决于是否有四个长度一样的木板
能否建造长方形取决于在减去四个长度一样的木板后,是否还至少两组两个长度一样的木板

用set1存放有四个长度一样的木板,用set2存放有两个长度一样的木板

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[N];
int n,q;
void solve() {
   
	cin>>n;
	set<int>s1,s2;
	map<int,int>mp;//桶计数
	for(int i=1;i<=n;i++){
   
		int x;
		cin>>x;
		mp[x]++;
		if(mp[x]>=2) s2.insert(x);
		if(mp[x]>=4) s1.insert(x);
 	}
	cin>>q;
	while(q--){
   
		char ch;
		int x;
		cin>>ch>>x;
		if(ch=='+'){
   
			mp[x]++;
			if(mp[x]>=2) s2.insert(x);
			if(mp[x]>=4) s1.insert(x);
		}
		else{
   
			mp[x]--;
			if(mp[x]<2) s2.erase(x);
			if(mp[x]<4) s1.erase(x);
		}
		if(s1.size()>=2) cout<<"Yes"<<endl;
		else if(s1.size()==1){
   
			int x=*s1.begin();
			if(mp[x]>=8) cout<<"Yes"<<endl;
			else if(mp[x]>=6){
   
				if(s2.size()>=2) cout<<"Yes"<<endl;
				else cout<<"No"<<endl;
			}
			else{
   
				if(s2.size()<=2) cout<<"No"<<endl;
				else 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;
}

12.C. Phoenix and Towers

一共有n块积木,高度为hi(高度小于等于x)

将n块积木堆成m座塔,任意两座塔的高度差要小于等于x
坑点:YES,NO

如果不能建造,就输出NO,否则输出YES,并输出每个积木放在哪座塔中

可能平均分比较好

将n个积木分配到m组,使得任意两组差小于等于x
先升个序,然后按顺序循环放置,通过手玩,验证,发现任意两组是不会超的,应该是不会无解的

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,m,x;
struct node{
   
	int a,idx;
	bool operator<(const node &W)const{
   
		return a<W.a;
	}
}q[N];
void solve() {
   
	cin>>n>>m>>x;
	for(int i=1;i<=n;i++) cin>>q[i].a,q[i].idx=i;
	sort(q+1,q+1+n);
	vector<int>ans(n+1);
	int mm=1;
	for(int i=1;i<=n;i++){
   
		ans[q[i].idx]=mm;
		mm++;
		if(mm==m+1) mm=1;
	}
	cout<<"YES"<<endl;
	for(int i=1;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;
}

13.A. Meximum Array

长度为n的数组a(数[0,n])

构造数组b
while(a不为空),在a中选择前k个个数,将MEX放入b的末尾
使得数组b字典序最大
贪心,优先选择MEX最大的

trick:

作为mex题目的一个案例

坑点:

贪心地一段一段截取,最后不能忘了最后一段

1.O(1)地得到x是否存在于后缀中:从左到右扫描, 维护每一个数字当前出现的次数和总的次数,如果当前数字出现次数和该数字总个数相等,说明后缀中没有该数字了,在这里是看当前的mex是否在后缀中出现过,如果没出现,那么就不会出现比当前mex更大的了,那么就贪心的结束

2.如何求mex:利用set维护mex,一边遍历,一边将其放入set中

while(s.count(mex)) mex++;

不用担心这样会超时,因为它是一边遍历一边放入set,一边维护mex,mex并不是每次从0开始加的

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N];
int n;
void solve() {
   
	cin>>n;
	map<int,int>mp,mmp;
	for(int i=1;i<=n;i++) cin>>a[i],mp[a[i]]++;
	set<int>s;
	vector<int>ans;
	int mex=0;
	for(int i=1;i<=n;i++){
   
		mmp[a[i]]++;
		s.insert(a[i]);
		while(s.count(mex)) mex++;
		if(mmp[mex]==mp[mex]){
   
			ans.push_back(mex);
			mex=0;
			s.clear();
		}
	}
	if(s.size()) ans.push_back(mex);
	cout<<ans.size()<<endl;
	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;
}

14.D. Productive Meeting

长度为n的数组a(数[0,2e5]),n大于等于2
ai表示社交能力,表示第i个人可以交谈几次

问一次会议最多可以交谈几次,并输出方案

贪心,优先消耗大的,如果先消耗小的,那么最大的只剩一个人了,没人和他交谈,那么次数都浪费掉了

坑点:

大于0的才能入队,因为入队是要去抵消的

trick:

1.往贪心考虑的思考方式可以是假设优先怎么样,如果不对那么就反过来,或者通过反面来衬托正面方法的优越性,想到了贪心的思路一定要造样例验证

2.优先消耗大的,想到优先队列大根堆,但是每次只能做一次操作,因为每做一次操作就会实时变化,最大的一直在变,千万不能一次操作就全部把最大和次大抵消了

#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 n;
void solve() {
   
	cin>>n;
	priority_queue<PII>q;
	for(int i=1;i<=n;i++){
   
		int x;
		cin>>x;
		if(x) q.push({
   x,i});
	}
	vector<PII>ans;
	while(q.size()>1){
   
		auto t1=q.top();
		q.pop();
		auto t2=q.top();
		q.pop();
		ans.push_back({
   t1.second,t2.second});
		t1.first--;
		t2.first--;
		if(t1.first>0) q.push(t1);
		if(t2.first>0) q.push(t2);
	}
	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;
}

15.B2. Wonderful Coloring - 2

长度为n的序列,一共有k种颜色
给n个数着色

如果两个数相等,则不能涂一样的颜色
每种颜色用的次数必须相等(一共k种颜色,用的次数都要相等)

要求尽可能涂的数量最多

坑点:

题目读仔细,一开始不知道k种颜色都要用

trick:

本质上是一个分配问题,本题作为分配问题的一个案例

要将k组都分配,那么就从第1组开始到第k组,一组一组放

由于两个相同的数不能放在同一组,所以将所有数排个序,连续放在一起,这样只要不超过k个,就不会出现相同的数放在一起,具体做法是

	map<int,int>mp;
	vector<PII>ans;
	for(int i=1;i<=n;i++){
    
		mp[a[i]]++;
		if(mp[a[i]]<=k) ans.push_back({
    a[i],i});
	}
sort(ans.begin(),ans.end());

另外,排序后获取下标也不一定非得用结构体,也可以用以上方法,vector存放pair类型

包括下面也作为案例,共group组,每组k个,每组都通过加法取模来得到1到k

	int group=ans.size()/k;
	for(int i=0;i<group*k;i++){
    
		c[ans[i].second]=i%k+1;
	}
#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 c[N];
int n,k;
void solve() {
   
	cin>>n>>k;
	memset(c,0,sizeof c);
	for(int i=1;i<=n;i++) cin>>a[i];
	map<int,int>mp;
	vector<PII>ans;
	for(int i=1;i<=n;i++){
   
		mp[a[i]]++;
		if(mp[a[i]]<=k) ans.push_back({
   a[i],i});
	}
	sort(ans.begin(),ans.end());
	int group=ans.size()/k;
	for(int i=0;i<group*k;i++){
   
		c[ans[i].second]=i%k+1;
	}
	for(int i=1;i<=n;i++) cout<<c[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;
}

16.C. Array Game

长度为n的数组a(数[1,1e18]) n大于等于2
操作:选取两个数,将两数的差的绝对值放在数组的末尾
k次操作

求数组a中最小值最小是多少

坑点:

没有想到可以再次选择两个同样的数,这样的话,三次一定可以得到0

其它只要讨论k为1以及k为2的情况

细细分析,为什么当时没想到,因为当时一开始就奔着贪心的思路去了,想着对两个差值最小的进行操作,实际上错了,但是方向已经偏了,所以,如果想到了贪心,一定要先验证,手玩造复杂极端的样例,如果不对,那么就立马纠正思路

或者说是如果是脑子里一看到就立马蹦出的思路,一定要验证,因为往往会被经验主义所带偏了方向

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e3+10;
int a[N];
int n,k;
void solve() {
   
	cin>>n>>k;
	int minn=1e18;
	for(int i=1;i<=n;i++) cin>>a[i],minn=min(minn,a[i]);
	if(k>=3){
   
		cout<<0<<endl;
		return;
	}
	sort(a+1,a+1+n);
	if(k==1){
   
		for(int i=2;i<=n;i++){
   
			minn=min(minn,a[i]-a[i-1]);
		}
		cout<<minn<<endl;
		return;
	}
	vector<int>ans;
	for(int i=1;i<n;i++){
   
		for(int j=i+1;j<=n;j++){
   
			minn=min(minn,abs(a[i]-a[j]));
			ans.push_back(abs(a[i]-a[j]));//操作一次得到的所有数,这是所有可能的情况,但是只能得到其中的一个
		}
	}
	sort(ans.begin(),ans.end());
	for(int i=1;i<=n;i++){
   
		int pos=lower_bound(ans.begin(),ans.end(),a[i])-ans.begin();
		if(pos<(int)ans.size()) minn=min(minn,abs(a[i]-ans[pos]));
		if(pos>0) minn=min(minn,abs(a[i]-ans[pos-1]));
	}
	cout<<minn<<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. Sum of Substrings

当1在最左端的时候,产生的贡献为1,当1在最右端的时候,产生的贡献为10,当1在中间的时候产生的贡献均为11

trick:

如果没有思路,就手玩造不同情况的复杂的极端的样例

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,k;
string s;
void solve() {
   
	cin>>n>>k;
	cin>>s;
	int l=-1,r=-1;
	map<char,int>mp;
	int cnt=0;
	for(int i=0;i<n;i++){
   
		if(s[i]=='1') cnt++;
	}
	for(int i=0;i<n;i++){
   
		if(s[i]=='1'){
   
			l=i;
			break;
		}
	}
	for(int i=n-1;i>=0;i--){
   
		if(s[i]=='1'){
   
			r=i;
			break;
		}
	}
	int res1=l,res2=n-1-r;
	if(cnt==0){
   
		cout<<0<<endl;
		return;
	}
	if(cnt==1){
   
		if(k>=res2) cout<<1<<endl;
		else if(k>=res1) cout<<10<<endl;
		else cout<<11<<endl;
		return;
	}
	if(k>=res1+res2) cout<<11+11*(cnt-2)<<endl;
	else if(k>=res2) cout<<1+11*(cnt-1)<<endl;
	else if(k>=res1) cout<<10+11*(cnt-1)<<endl;
	else cout<<11*cnt<<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. Diverse Garland

n个灯
颜色已知,共R,G,B三种颜色
重新着色,使得相邻的灯颜色不同
问最少需要重新着色几个灯管

贪心,遍历,如果灯和前一个灯颜色相等,就要重新上色

trick:

一开始题目没读懂,题目一边读一边用自己的话记录,不存在读不懂的题目,问题在于读题时浮躁

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
string tmp="RGB";
string s;
void solve() {
   
	cin>>n>>s;
	int ans=0;
	for(int i=1;i<n-1;i++){
   
		if(s[i]==s[i-1]){
   
			ans++;
			for(int j=0;j<3;j++){
   
				if(tmp[j]!=s[i-1]&&tmp[j]!=s[i+1]) s[i]=tmp[j];
			}
		}
	}
	if(s[n-1]==s[n-2]){
   
		ans++;
		for(int j=0;j<3;j++){
   
			if(tmp[j]!=s[n-2]) s[n-1]=tmp[j];
		}
	}
	cout<<ans<<endl;
	cout<<s<<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.D. Absolute Sorting

长度为n的数组a(数[1,1e8])n大于等于2
操作:选择一个整数x,将所有的ai替换为ai和x的差的绝对值,使得非降序
问有无这样的x,无解输出-1
如果有解,x在[0,1e9]

画一个数轴,从前往后遍历,如果前一个数小于后一个数,那么x必须选取在它们中间的左边
如果前一个数大于后一个数,那么x必须选取在它们中间的右边
一直取交集,如果交集为空,则无解
如果前一个数和后一个数相等,那么无论x取什么都可以,不能设区间

坑点:

如果前一个数和后一个数相等,那么无论x取什么都可以,不能设区间

trick:

1.没有思路就手玩,这题也是手玩做出来的

2.当出现差的绝对值时,可以画一个数轴,转化为点与点之间的距离,数形结合

3.非降序是整体的一个结果,我们聚焦到局部,如果相邻两两之间都非降序,那么整个序列自然就非降序了(整体->局部),但是注意必须始终保证朝一个方向遍历,只能修改后面的值,不能把前面的值修改

3.若要确定出一个数满足条件,我们可以以区间的思想,它可以在哪个范围内选择,在各种条件限制下,不断缩小区间,取交集

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N];
int n;
void solve() {
   
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	int l=0,r=1e9;
	for(int i=1;i<=n-1;i++){
   
		if(a[i]==a[i+1]) continue;
		if(a[i]<a[i+1]){
   
			int rr=(a[i]+a[i+1])/2;
			r=min(r,rr);
		}
		else{
   
			int ll=(a[i]+a[i+1]+1)/2;
			l=max(l,ll);
		}
	}
	if(l<=r) cout<<l<<endl;
	else 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;
}

20.C. LIS or Reverse LIS?

长度为n的数组a(数[1,1e9])
beauty:数组a最长上升子序列的长度和最长下降子序列的长度取min
重新排列a,求最大的beauty

其中一个大,必然会导致另一个小,那么min也小
所以最好平均一下

与顺序无关,先排个序
然后如果每个数字都只有一个的话,那么肯定平均一下,答案为(数字种数+1)/2
但是如果每种数字不止一个,那么就可以把多余的数字往后放使得降序,这样的话,答案为max((数字的种数+1)/2,mp[digit]大于1的个数)
如果n为1的话,那么答案为1

大概思路是对的,但是应该分开来,cnt1为只有一个的数字,cnt2有多个数字的数字,答案为(cnt1+1)/2+cnt2
只有一个的数字应该取平均分,有多个的数字两边都可以分到
归结为两个集合,然后考虑哪些数字应该放在哪个集合中,以及顺序怎么放

trick:

作为一个案例

归结为两个集合,然后考虑哪些数字应该放在哪个集合中,以及顺序怎么放(这种题目,每一个数字只能选择其中一个集合放置)

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N];
int n;
void solve() {
   
	cin>>n;
	map<int,int>mp;
	int maxn=0;
	for(int i=1;i<=n;i++) cin>>a[i],mp[a[i]]++,maxn=max(maxn,a[i]);
	if(n==1){
   
		cout<<1<<endl;
		return;
	}
	int cnt1=0,cnt2=0;
	for(auto v:mp){
   
		if(v.second==1) cnt1++;
		else cnt2++;
	}
	cout<<(cnt1+1)/2+cnt2<<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.C. Not Assigning

一共有n个顶点
树的n-1条边

给n-1条边赋值,使得树成为质数树
质数树:每条边都是质数,相邻两条边的和也是质数

如果有解,权值范围在[1,1e5]
如果无解,输出-1

想到用质数2,3
如果一个顶点有三个分支,肯定不行,因为质数除了2是偶数,其它都是奇数,两个奇数相加为偶数就不不是质数了,所以只能一个2,再一个奇数质数,再一个2,这样2+2=4也不是质数了,所以一个顶点最多两个分叉,即度数最大为2,所以只有一条单向路才有解,直接2,3,2,3交替赋值即可

记录两个顶点所连边的序号
从叶子节点开始dfs,2,3,2,3交替赋值给当前边的序号

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
const int N=1e5+10;
vector<vector<int>>e(N);
int d[N];
int ans[N];
int n;
map<PII,int>mp;
void dfs(int u,int fa,int digit){
   
	ans[mp[{
   u,fa}]]=digit;
	for(auto v:e[u]){
   
		if(fa==v) continue;
		dfs(v,u,5-digit);
	}
}
void solve() {
   
	cin>>n;
	memset(d,0,sizeof d);
	memset(ans,0,sizeof ans);
	mp.clear();
	for(int i=1;i<=n;i++) e[i].clear();
	for(int i=0;i<n-1;i++){
   
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
		mp[{
   u,v}]=mp[{
   v,u}]=i+1;
		d[u]++;
		d[v]++;
	}
	for(int i=1;i<=n;i++){
   
		if(d[i]>=3){
   
			cout<<-1<<endl;
			return;
		}
	}
	int st;
	mp[{
   st,0}]=0;
	for(int i=1;i<=n;i++){
   
		if(d[i]==1){
   
			st=i;
			break;
		}
	}
	dfs(st,0,3);
	for(int i=1;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;
}

22.C. awoo’s Favorite Problem

长度为n的字符串s和t,均由a,b,c组成
操作:将ab变成ba或者将bc变成cb
次数不限
问能否使s变成目标串t

a和c只能通过b进行移动,a和c彼此分割,所以将b去掉,字符串s和t应该是相等的

字符串s和t中字符a和c所处的对应的位置应该满足:s中的a不能在t中的a的前面,s中的c不能在t中的c的前面

trick:

给定串变成目标串问题

重点关注操作,手玩造不同情况的复杂的极端的样例,重点关注操作的对象,无非是ab和bc,将它们再分解成最基本的,无非是a,b,c三个基本单元,重点关注基本单元的变化

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<char,int>PII;
int n;
string s,t;
void solve() {
   
	cin>>n;
	cin>>s>>t;
	string tmp1,tmp2;
	vector<PII>ans1,ans2;
	for(int i=0;i<n;i++){
   
		if(s[i]!='b') tmp1+=s[i],ans1.push_back({
   s[i],i});
		if(t[i]!='b') tmp2+=t[i],ans2.push_back({
   t[i],i});
	}
	if(tmp1!=tmp2){
   
		cout<<"NO"<<endl;
		return;
	}
	for(int i=0;i<(int)ans1.size();i++){
   
		if(ans1[i].first=='a'){
   
			if(ans2[i].second<ans1[i].second){
   
				cout<<"NO"<<endl;
				return;
			}
		}
		else{
   
			if(ans2[i].second>ans1[i].second){
   
				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;
}

23.E. Vlad and a Pair of Numbers

加法恒等式:a+b=(a^b)+ 2 * (a&b)

由于a+b = 2 * (a^b),得a ^ b=2 * (a&b)= x

构造a和b

如果a^b为奇数,无解

从与以及异或本身的性质来看,如果a&b和a^b如果有任意一位同时为1,则 无解

a=(a&b)+(a^b)=x*(3/2)

b=a&b=x/2

trick:

1.加法恒等式:a+b=(a^b)+ 2 * (a&b)

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

比如说a&b中为1的位,a和b对应的该位均为1,然后其它位可一个为0,一个为1,也可以两个都为0,我们如果是构造的话,可以不用管0,所以构造a=b=a&b

再比如说ab中为1的位,a对应该位为1,b对应该位为0或者a对应该位为0,b对应该位为1,总之,一个为0,一个为1,我们如果是构造的话,只要满足一个为0,一个为1即可,那么我们可以把所有的1分给a,那么a为(a&b)或上(ab)

另外,(a&b)|(ab)=(a&b)+(ab),如果是或运算的话,只要没有位均为1,就可以直接加,无进位加法

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int x;
void solve() {
   
	cin>>x;
	if(x%2){
   
		cout<<-1<<endl;
		return;
	}
	if(x&(x/2)){
   
		cout<<-1<<endl;
		return;
	}
	cout<<(x|(x/2))<<' '<<x/2<<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. Make Good

长度为n的数组a(数[0,1e9])
好:所有数的和等于所有数异或的两倍

最多添加三个不同的元素,使数组变好,一定有解

a+b+c=sum
abc=pre

a+b+c+pre=sum+pre
abcpre=prepre=0

a+b+c+pre+sum+pre=2*(sum+pre)

abcpre(sum+pre)=sum+pre

trick:

异或性质:

  1. x^0=x
  2. x^x=0
#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;
	int sum=0,pre=0;
	for(int i=1;i<=n;i++) cin>>a[i],sum+=a[i],pre^=a[i];
	if(sum==2*pre){
   
		cout<<0<<endl<<endl;
		return;
	}
	cout<<2<<endl;
	cout<<pre<<' '<<sum+pre<<endl;
}
signed main() {
   
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
   
		solve();
	}
	return 0;
}

25.B. AND Sequences

长度为n的数组a(数[0,1e9])n大于等于2
良好序列:[a1,ai]全部与起来=[ai+1,an]全部与起来,对于i从1到n-1

对数组a重新排序,问几种序列是良好序列,mod 1e9+7

a[1]=a[2]a[3]…^a[n]

a[1]=a[1]a[1]=a[1]a[2]a[3]…a[n],

a[n]=a[1]a[2]a[3]^…a[n-1]

a[n]=a[n]a[n]=a[1]a[2]a[3]…a[n],

所以a[1]=a[n]=tmp=a[1]a[2]a[3]^…a[n]

由于a[1]和a[n]是所有数的与,所以中间可以随意放

trick:

1.对于位运算,更多的是一种数学式子上的东西,重在推导,所以把可得到的式子整整齐齐的一个一个列出来

2.关于位运算的题目,经常全部进行一次位运算

3.a & a = a

4.如果数组中的数全部都与起来等于tmp,那么tmp和任何一个数(数组当中的一个数)与,都等于tmp(根据第3点)

#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];
int n;
void solve() {
   
	cin>>n;
	map<int,int>mp;
	for(int i=1;i<=n;i++) cin>>a[i],mp[a[i]]++;
	int tmp=a[1];
	for(int i=1;i<=n;i++) tmp&=a[i];
	int ans=1;
	ans=mp[tmp]*(mp[tmp]-1);
	ans%=mod;
	for(int i=n-2;i>=1;i--){
   
		ans=ans*i%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;
}

26.B. Suffix Operations

长度为n的数组(数[-5e8,5e8])n大于等于2
操作:将后缀所有元素加1或者减1
可以帮助他,可以修改一个整数(所有操作之前)
使得所有元素相等,问最少执行多少次操作

可以发现,一起变的数是同步增长或者同步减少的,对目标全部元素相等毫无帮助,所以一起进行操作的数应该是相等的,所以就从最后一个数开始,先让它变得和倒数第二个数相等,再让他们变得和倒数第三个数相等,一直到和第一个数相等

所以只要看相邻两个数的差,我们可以帮助他修改一个数,就看相邻两个数差最大的,变成0就行,不对,比如说
99 96 97 95,如果将96变成99,原本97到96需要1,96到99需要3,现在97到99需要2,所以应该看的是,将某个数改成相邻的数之后,相当于去掉这个数之后,差值减的最多的,所以只要分别枚举删除哪一个数

trick:
一边手玩一边关注被操作对象的变化,以它们之间的联系

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N];
int n;
void solve() {
   
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	int sum=0;
	int ans=1e18;
	for(int i=n;i>=2;i--) sum+=abs(a[i]-a[i-1]);
	ans=min(ans,sum-abs(a[1]-a[2]));
	ans=min(ans,sum-abs(a[n]-a[n-1]));
	for(int i=2;i<=n-1;i++){
   //枚举删除哪一个数
		ans=min(ans,sum-(abs(a[i]-a[i-1])+abs(a[i]-a[i+1]))+abs(a[i-1]-a[i+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;
}

27.D1. Mocha and Diana (Easy Version)

摩卡有节点1到n,戴安娜有节点1到n
初始边数不同,摩卡有m1条边,戴安娜有m2条边

问最多添加多少条边(两人的边必须同时添加,而且添加的边是一样的)

不在一个连通块就可以相连
数据比较小,直接暴力,O(n^2)

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
const int N=1010;
int p1[N],p2[N];
int n,m1,m2;
int find1(int x){
   
	if(p1[x]!=x) p1[x]=find1(p1[x]);
	return p1[x];
}
int find2(int x){
   
	if(p2[x]!=x) p2[x]=find2(p2[x]);
	return p2[x];
}
void solve() {
   
	cin>>n>>m1>>m2;
	for(int i=1;i<=n;i++) p1[i]=p2[i]=i;
	while(m1--){
   
		int u,v;
		cin>>u>>v;
		u=find1(u),v=find1(v);
		p1[u]=v;
	}
	while(m2--){
   
		int u,v;
		cin>>u>>v;
		u=find2(u),v=find2(v);
		p2[u]=v;
	}
	vector<PII>ans;
	for(int i=1;i<=n;i++){
   
		for(int j=1;j<=n;j++){
   
			if(i==j) continue;
			int a1=find1(i),b1=find1(j);
			int a2=find2(i),b2=find2(j);
			if(a1!=b1&&a2!=b2){
   
				p1[a1]=b1;
				p2[a2]=b2;
				ans.push_back({
   i,j});
			}
		}
	}
	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;
}

28.C. Fillomino 2

共n个整数
n * n 的方阵,主对角线上放置1到n的排列
满足,1的连通块元素个数为1,2的连通块元素个数为2,以此类推

我们从上到下,数x,先往左看,如果放不了,就往下放,直到放置了刚好x个

trick:

如果样例中没有无解的情况,大概是没有无解的情况

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=510;
int p[N][N];
int n;
void solve() {
   
	cin>>n;
	memset(p,0,sizeof p);
	for(int i=1;i<=n;i++) cin>>p[i][i];
	for(int i=1;i<=n;i++){
   
		int x=i,y=i;
		int cnt=1;
		while(1){
   
			if(cnt==p[i][i]) break;
			if(y-1>=1&&p[x][y-1]==0){
   
				y--;
				p[x][y]=p[i][i];
				cnt++;
				if(cnt==p[i][i]) break;
			}
			else if(x+1<=n&&p[x+1][y]==0){
   
				x++;
				p[x][y]=p[i][i];
				cnt++;
				if(cnt==p[i][i]) break;
			}
			if(cnt==p[i][i]) break;
		}
	}
	for(int i=1;i<=n;i++){
   
		for(int j=1;j<=i;j++){
   
			cout<<p[i][j]<<' ';
		}
		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.A1. Dual (Easy Version)

长度为n的数组(数[-20,20])n小于等于20 数据比较小,考虑暴力

操作:将一个数加到另一个数上,可以自己加到自己

使非降序,最多50次操作就够了

如果都是负数,那么从后往前,后一个数加到前一个数上
如果都是正数,那么从前往后,前一个数加到后一个数上
如果有一个数是正数,那么就一直加自己加到大于20,然后每个数都加它,可以让每个数快速变成正数

trick:

如果操作对象有两个,考虑让一个定死,或者让两个都定死

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
const int N=25;
int a[N];
int n;
void solve() {
   
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	vector<PII>ans;
	int maxn=-25;
	int maxi=0;
	for(int i=1;i<=n;i++){
   
		if(a[i]>maxn){
   
			maxn=a[i];
			maxi=i;
		}
	}
	if(maxn<=0){
   
		for(int i=n-1;i>=1;i--){
   
			ans.push_back({
   i,i+1});
		}
	}
	else{
   
		while(maxn<=20){
   
			ans.push_back({
   maxi,maxi});
			maxn*=2;
		}
		a[maxi]=maxn;
		for(int i=1;i<=n;i++){
   
			if(a[i]<0){
   
				ans.push_back({
   i,maxi});
				a[i]+=a[maxi];
			}
		}
		for(int i=1;i<=n-1;i++){
   
			ans.push_back({
   i+1,i});
		}
	}
	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;
}

30.C. No Prime Differences

n行m列的网格填入1到n * m
使得任意相邻两个数的差的绝对值不是质数,一定有解

行和列有一个为偶数,那么就可以1到n * m顺序填

如果m不是是质数,那么一个一个按顺序填

否则,先把所有偶数行输出,再把所有奇数行输出

trick:

1.注意一定要看数据范围,记下来,在这里n和m均大于等于4

2.构造题,往奇偶方向想,如果是矩阵构造题,往奇偶行方向想,在这里提供一种方法 ,先把所有偶数行输出,再把所有奇数行输出(相对于从1开始顺序填的矩阵)

3.矩阵构造题,一般是一边遍历一边输出,如果想把它们存在数组正确的位置上再全部输出比较困难

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,m;
bool check(int x){
   
	if(x<=1) return false;
	for(int i=2;i<=x/i;i++){
   
		if(x%i==0) return false;
	}
	return true;
}
void solve() {
   
	cin>>n>>m;
	if(!check(m)){
   
		for(int i=1;i<=n;i++){
   
			for(int j=1;j<=m;j++){
   
				cout<<(i-1)*m+j<<' ';
			}
			cout<<endl;
		}
	}
	else{
   
		for(int i=2;i<=n;i+=2){
   
			for(int j=1;j<=m;j++) cout<<(i-1)*m+j<<' ';
			cout<<endl;
		}
		for(int i=1;i<=n;i+=2){
   
			for(int j=1;j<=m;j++) cout<<(i-1)*m+j<<' ';
			cout<<endl;
		}
	}
	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;
}

31.C. Matching Arrays

长度为n的数组a和b,数[1,2*n]

重新排列b,使得ai大于bi的个数刚好为x,无解输出No

贪心,a中x个最大的和b中x个最小的匹配,先保证有x个,并不是a中最大的和b中最小的,而是a中x个最大的升个序,b中x个最小的升个序,然后按顺序匹配,剩下的同理,为什么要升序后按顺序比,可以通过反向思考来感受这样做的合理性

trick:

1.贪心的思考方式:通过反向思考来感受贪心是否合理

2.两两匹配问题

分为三种

第一种是序列内部进行两两匹配,比如说最小和最大匹配,次小和次大匹配

第二种是两个不同的序列进行匹配,比如说每次匹配差值最大的,要么序列a的最大和序列b的最小匹配,要么序列a的最小和序列b的最大匹配,再比如说要求是ai大于bi刚好有x个,那么需要a中最大的x个去和b中最小的x个匹配,保证有x个,但不是a的最大和b的最小匹配,而是分别升序,然后按顺序匹配,合理性通过反向思考感受

第三种是两个一样的序列进行匹配,一般是通过自身元素的两两交换来满足某个要求,本质上就是自身和自身匹配

#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,xx;
void solve() {
   
	cin>>n>>xx;
	vector<PII>ans;
	for(int i=0;i<n;i++){
   
		int x;
		cin>>x;
		ans.push_back({
   x,i});
	}
	multiset<int>s;
	multiset<int,greater<int>>s1,s2;
	for(int i=0;i<n;i++){
   
		int x;
		cin>>x;
		s.insert(x);
	}
	sort(ans.begin(),ans.end());
	reverse(ans.begin(),ans.end());
	for(int i=0;i<xx;i++){
   
		s1.insert(*s.begin());
		s.erase(s.begin());
	}
	while(s.size()){
   
		s2.insert(*s.begin());
		s.erase(s.begin());
	}
	for(int i=0;i<xx;i++){
   
		if(*s1.begin()>=ans[i].first){
   
			cout<<"No"<<endl;
			return;
		}
		a[ans[i].second]=*s1.begin();
		s1.erase(s1.begin());
	}
	for(int i=xx;i<n;i++){
   
		if(*s2.begin()<ans[i].first){
   
			cout<<"No"<<endl;
			return;
		}
		a[ans[i].second]=*s2.begin();
		s2.erase(s2.begin());
	}
	cout<<"Yes"<<endl;
	for(int i=0;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;
}

32.E. Add Modulo 10

长度为n的数组a(数[0,1e9])
操作:ai加上ai%10(可以多次对同一下标)
操作次数不限
问是否可以使所有元素相等

每次都只加个位,所以个位上的数字只要看个位即可

0
1->2->4->8->6->2
2->4->8->6->2
3->6->2->4->8->6->2
5->0

除了0和5特殊一些,其它均可以进入到2->4->8->6的循环中,并且从2到2会增加20,比如12->14->18->26->32

trick:

每次都只加个位,所以个位上的数字只要看个位即可

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+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]%10]++;
	if(mp[0]||mp[5]){
   
		for(int i=1;i<=n;i++){
   
			if(a[i]%10!=0&&a[i]%10!=5){
   
				cout<<"No"<<endl;
				return;
			}
		}
		for(int i=1;i<=n;i++){
   
			if(a[i]%10==5) a[i]+=5;
		}
		for(int i=2;i<=n;i++){
   
			if(a[i]!=a[i-1]){
   
				cout<<"No"<<endl;
				return;
			}
		}
		cout<<"Yes"<<endl;
		return;
	}
	for(int i=1;i<=n;i++){
   
		while(a[i]%10!=2){
   
			a[i]+=a[i]%10;
		}
		a[i]%=20;
	}
	for(int i=2;i<=n;i++){
   
		if(a[i]!=a[i-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;
}

33.D. Bracket Coloring

长度为n的字符串

分组,可以跳着挑,使得每组括号轴对称
问最少分几组

trick:

对于括号序列判断合法的问题,我们常用前缀和判断,遇到左括号 +1 ,遇到右括号 −1

一个括号(左边是左括号,右边得有右括号和左括号抵消)是合法的 ⟺ 对于所有的前缀 s u m i sum_i sumi 都满足 s u m i sum_i sumi≥0 ,且最后 s u m i sum_i sumi=0 。

得到折线图

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

而以下这一种是右括号在左边,右边有左括号和右括号抵消的合法括号序列的折线图

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

于是得到以下这种,当sum大于等于0时,是第一种的合法括号序列,当sum小于等于0时,是第二种的合法括号序列

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

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int ans[N];
int n;
string s;
void solve() {
   
	cin>>n;
	cin>>s;
	int sum=0;
	int flag=0;
	for(int i=0;i<n;i++){
   
		if(s[i]=='(') sum++;
		else sum--;
		if(sum>0) ans[i]=1,flag|=1;
		else if(sum<0) ans[i]=2,flag|=2;
		else if(sum==0) ans[i]=ans[i-1];
	}
	if(sum){
   
		cout<<-1<<endl;
		return;
	}
	if(flag!=3){
   
		cout<<1<<endl;
		for(int i=0;i<n;i++) cout<<1<<' ';
		cout<<endl;
		return;
	}
	cout<<2<<endl;
	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;
}

34.D1. Zero-One (Easy Version)

长度为n的01串a和b

操作:选择两个索引,01反转,如果两个索引相邻,那么代价为x,否则代价为y(x大于等于y)
操作不限次数
问a等于b的最小成本,无解输出-1

首先,需要反转的个数为奇数的话,那么无解

如果需要反转的个数为2的话,如果相邻的话,那么代价为min(x,2y),如果不相邻的话,那么代价为y
如果需要反转的个数大于2的话,那么代价为cnt/2
y

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,x,y;
string a,b;
void solve() {
   
	cin>>n>>x>>y;
	cin>>a>>b;
	vector<int>ans;
	for(int i=0;i<n;i++){
   
		if(a[i]!=b[i]) ans.push_back(i);
	}
	if(ans.size()%2){
   
		cout<<-1<<endl;
		return;
	}
	if(ans.size()==2){
   
		if(ans[1]==ans[0]+1){
   
			cout<<min(x,2*y)<<endl;
		}
		else cout<<y<<endl;
	}
	else{
   
		cout<<ans.size()/2*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;
}

35.C. Palindromifier

长度为n的只包含小写字母的字符串
操作:
二选一
1.将2到i反向加到s的前面
2.将i到n-1反向加到s的后面

将字符串变成回文串,最多操作30次,一定有解

有一种万能的方法:比如abcd,先将c移到最右边,变成abcdc,然后将bcd反向移到最左边,变成dcbabcdc,然后将第2个c移到最左边变成cdcbabcdc

trick:

重点关注操作对象,比如可以对连续一段进行操作,我们也可以考虑对单个进行操作

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
string s;
void solve() {
   
	cin>>s;
	int len=s.size();
	cout<<3<<endl;
	cout<<'R'<<' '<<len-1<<endl;
	len++;
	cout<<'L'<<' '<<len-1<<endl;
	cout<<'L'<<' '<<2<<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. Hossam and Friends

他有n个朋友,队列1到n
共m对互不相识的朋友

队列子段a到b如果都是好朋友,那么该子段就是好队列
问有几个子段是好队列

如果全部人都认识的话,那么第一个人作为区间右端点,有一个区间,第二个人作为区间右端点,有两个区间,…第i个人作为区间右端点,有i个区间

第i个人作为区间右端点,看它最多往左延申到哪里,看它左边包括它自己不相识的人的最大值取max

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,m;
void solve() {
   
	cin>>n>>m;
	memset(a,0,sizeof a);
	for(int i=0;i<m;i++){
   
		int x,y;
		cin>>x>>y;
		if(x>y) swap(x,y);
		a[y]=max(a[y],x);
	}
	int ans=0;
	for(int i=1;i<=n;i++)
	{
   
		a[i]=max(a[i],a[i-1]);
		ans+=i-a[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;
}

37.C. Column Swapping

trick:

1.vector<vector>a(n,vector(m));可以直接cin>>a [i] [j]

2.如果要通过交换两个元素将一个序列排成非降序,可以另外放一个数组,先排好序,然后如果对应位置上的数不一样,那么这些位置需要交换

for(int i=0;i<n;i++){
    
		sort(b[i].begin(),b[i].end());
	}
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,m;
void solve() {
   
	cin>>n>>m;
	vector<vector<int>>a(n,vector<int>(m));
	vector<vector<int>>b(n,vector<int>(m));
	for(int i=0;i<n;i++){
   
		for(int j=0;j<m;j++){
   
			cin>>a[i][j];
			b[i][j]=a[i][j];
		}
	}
	for(int i=0;i<n;i++){
   
		sort(b[i].begin(),b[i].end());
	}
	int l=-1,r=-1;
	for(int i=0;i<n;i++){
   
		for(int j=0;j<m;j++){
   
			if(a[i][j]!=b[i][j]){
   
				l=j;
				break;
			}
		}
		for(int j=m-1;j>=0;j--){
   
			if(a[i][j]!=b[i][j]){
   
				r=j;
				break;
			}
		}
		if(l!=-1&&r!=-1) break;
	}
	if(l==-1&&r==-1){
   
		cout<<1<<' '<<1<<endl;
		return;
	}
	for(int i=0;i<n;i++){
   
		swap(a[i][l],a[i][r]);
	}
	for(int i=0;i<n;i++){
   
		for(int j=0;j<m;j++){
   
			if(a[i][j]!=b[i][j]){
   
				cout<<-1<<endl;
				return;
			}
		}
	}
	cout<<l+1<<' '<<r+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;
}

38.A. Bear and Prime 100

交互题

任何一个合数都可以分解为若干个质数的乘积(唯一)

trick:
这是做到的第二个交互题,当作案例

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int prime[]={
   2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,4,9,25,49};
void solve() {
   
	int cnt=0;
	for(int i=0;i<19;i++){
   
		cout<<prime[i]<<endl;
		cout.flush();
		string s;
		cin>>s;
		if(s[0]=='y') cnt++;
	}
	if(cnt<2) cout<<"prime"<<endl;
	else cout<<"composite"<<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.D. Row Major

构造一个长度为n的字符串
使得它排成任何矩阵的形状,都没有相邻的字符相等
字符个数要求最少
如果n为奇数,那么直接两个字符ab,一直循环
如果n为偶数,那么找到第一个x,满足x不是n的因数,x-1是n的因数,x个字符一直循环

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
   
	cin>>n;
	if(n%2){
   
		int flag=1;
		for(int i=0;i<n;i++){
   
			if(flag) cout<<'a';
			else cout<<'b';
			flag^=1;
		}
	}
	else{
   
		int num=1;
		for(int i=3;;i++){
   
			if(n%i&&n%(i-1)==0){
   
				num=i;
				break;
			}
		}
		int cnt=0;
		for(int i=0;i<n;i++){
   
			cout<<(char)('a'+cnt);
			cnt++;
			if(cnt==num) cnt=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;
}

40.C. Fishingprince Plays With Array

长度为n的数组a
操作:
1.在数组a中选择一个m的倍数,将其变为m个ai/m
2.对于刚好连续m个相同的元素,用m*ai一个元素换掉m个元素
操作次数不限

问能否将a变成b

如果是m的倍数,那么就一直除以m,直到不是m的倍数,不能再分解,计算其最小单位的个数,以此类推

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=5e4+10;
int a[N],b[N];
int n,m;
int k;
void solve() {
   
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	a[n+1]=2e9;
	cin>>k;
	for(int i=1;i<=k;i++) cin>>b[i];
	b[k+1]=2e9;
	vector<int>ans1,ans2;
	map<int,int>mp1,mp2;
	int cnt=0;
	int sum=1;
	while(a[1]%m==0){
   
		a[1]/=m;
		sum*=m;
	}
	cnt+=sum;
	for(int i=2;i<=n+1;i++){
   
		sum=1;
		while(a[i]%m==0){
   //分解为sum份a[i],a[i]已经分解成最小单位了
			a[i]/=m;
			sum*=m;
		}
		if(a[i]==a[i-1]) cnt+=sum;
		else{
   
			ans1.push_back(a[i-1]);
			int len=ans1.size()-1;
			mp1[len]=cnt;
			cnt=sum;
		}
	}
	cnt=0;
	sum=1;
	while(b[1]%m==0){
   
		b[1]/=m;
		sum*=m;
	}
	cnt+=sum;
	for(int i=2;i<=k+1;i++){
   
		sum=1;
		while(b[i]%m==0){
   //分解为sum份a[i],a[i]已经分解成最小单位了
			b[i]/=m;
			sum*=m;
		}
		if(b[i]==b[i-1]) cnt+=sum;
		else{
   
			ans2.push_back(b[i-1]);
			int len=ans2.size()-1;
			mp2[len]=cnt;
			cnt=sum;
		}
	}
//	for(int i=0;i<(int)ans1.size();i++) cout<<ans1[i]<<' ';
//	cout<<endl;
//	for(int i=0;i<(int)ans2.size();i++) cout<<ans2[i]<<' ';
//	cout<<endl;
//	for(int i=0;i<(int)ans1.size();i++) cout<<mp1[i]<<' ';
//	cout<<endl;
//	for(int i=0;i<(int)ans2.size();i++) cout<<mp2[i]<<' ';
//	cout<<endl;
	if(ans1.size()!=ans2.size()){
   
		cout<<"No"<<endl;
		return;
	}
	for(int i=0;i<(int)ans1.size();i++){
   
		if(ans1[i]!=ans2[i]||mp1[i]!=mp2[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;
}

41.B. String Modification

长度为n的字符串
操作:对于所有长度为k的连续子串,从左到右依次颠倒

使得操作后的字典序最小,且选取的k尽量小

数据稍小,可以浅暴力,结果超时了,数据不算小,最多n^2
首先,需要将最小的字符放在第一个位置
所以先找到最小字符的位置

但是如果按照题目的步骤来的话必定会超时,然后通过找规律发现,最终结果是sk到sn,返回如果n和k奇偶性相同则加上sk-1sk-2…s1,否则加上s1s2…sk-1

trick:

如果按照题目的步骤很繁琐,必定会超时,那么可能可以手玩找规律直接一步得到经过那么多操作后的结果,做法是多手玩几个例子,得到最终的结果

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
string s;
void solve() {
   
	cin>>n;
	cin>>s;
	char ch='z';
	for(int i=0;i<n;i++){
   
		if(ch>s[i]) ch=s[i];
	}
	vector<int>ans;
	for(int i=0;i<n;i++){
   
		if(s[i]==ch) ans.push_back(i);
	}
//	for(auto v:ans) cout<<v<<' ';
//	cout<<endl;
	string tmp;
	int k=1;
	for(int i=0;i<n;i++) tmp+='z'; 
	for(auto v:ans){
   //第v个位置为最小字符
		int len=v+1;
		string ss;
		for(int i=v;i<n;i++) ss+=s[i];
		string sss;
		for(int i=0;i<v;i++) sss+=s[i];
		if(n%2==len%2) reverse(sss.begin(),sss.end());
		ss+=sss;
		if(tmp>ss){
   
			k=len;
			tmp=ss;
		}
	}
	cout<<tmp<<endl;
	cout<<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;
}

42.C. Bricks and Bags

题解

1到n共n块转
ai为第i块转的重量
共三个集合,将n块砖分配到三个集合中,集合不能为空
分配完后,从三个集合中各取出一个砖块,得分为w1和w2的差的绝对值加上w2和w3的差的绝对值
然后设x为最少得分,问x最大为多少

集合分配问题,考虑将哪些数放在哪个集合中

考虑让两边和中间的差值大,中间放最大的或者最小的,但这样并不一定是最优的

trick:

比较巧妙,当作案例

1.想到贪心的思路,要先验证,构造极端复杂的样例,可以这样构造:差值超级大和差值超级小结合

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;
void solve() {
   
	cin>>n;
	set<int>s;
	for(int i=1;i<=n;i++){
   
		cin>>a[i];
		s.insert(a[i]);
	}
	vector<int>ans;
	while(s.size()){
   
		ans.push_back(*s.begin());
		s.erase(s.begin());
	}
	int len=ans.size();
	if(len==1){
   
		cout<<0<<endl;
		return;
	}
	if(len==2){
   
		cout<<2*(ans[1]-ans[0])<<endl;
		return;
	}
	int res=ans[len-1]-ans[0]+ans[len-1]-ans[len-2];
	for(int i=1;i<len;i++){
   
		res=max(res,ans[i]-ans[0]+ans[i]-ans[i-1]);
	}
	res=max(res,ans[len-1]-ans[0]+ans[1]-ans[0]);
	for(int i=0;i<len-1;i++){
   
		res=max(res,ans[len-1]-ans[i]+ans[i+1]-ans[i]);
	}
	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;
}

43.D. Equalize Them All

操作:选择相邻的两个数,让第一个数加上或减去它们差的绝对值
问最少几次操作,可以让所有元素相等,一定有解

操作1的意思其实是将小的数变成大的数,操作2的意思其实是将大的数变成小的数
看哪个数最多,那么就变得全部和它一样

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
typedef pair<int,int>PII;
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 num;
	int cnt=0;
	for(auto v:mp){
   
		if(cnt<=v.second){
   
			cnt=v.second;
			num=v.first;
		}
	}
	set<int>s;
	for(int i=1;i<=n;i++){
   
		if(a[i]==num) s.insert(i);
	}
	cout<<n-cnt<<endl;
	int l=1;
	int en=(*(--s.end()));
	while(s.size()){
   
		int pos=*s.begin()-1;
		while(pos>=l){
   
			if(a[pos]<num) cout<<1<<' '<<pos<<' '<<pos+1<<endl;
			else cout<<2<<' '<<pos<<' '<<pos+1<<endl;
			pos--;
		}
		l=*s.begin()+1;
		s.erase(s.begin());
	}
	if(en<n){
   
		for(int i=en+1;i<=n;i++){
   
			if(a[i]<num) cout<<1<<' '<<i<<' '<<i-1<<endl;
			else cout<<2<<' '<<i<<' '<<i-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;
}

44.A. Grid game

竖着的在第一列抵消,横着的在第三行抵消

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
string s;
void solve() {
   
	cin>>s;
	int n=s.size();
	bool ok1=false,ok2=false;//ok1表示竖着的有没有放在(1,1),ok2表示横着的有没有放在(3,3)
	for(int i=0;i<n;i++){
   
		if(s[i]=='0'){
   
			if(!ok1) cout<<1<<' '<<1<<endl,ok1=true;
			else cout<<3<<' '<<1<<endl,ok1=false;
		}
		else{
   
			if(!ok2) cout<<3<<' '<<3<<endl,ok2=true;
			else cout<<3<<' '<<1<<endl,ok2=false;
		}
	}
}
signed main() {
   
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
   
		solve();
	}
	return 0;
}

45.B. Pasha and String

关于对称轴翻转,只要看左边一半就行,记录翻转的次数

利用差分和前缀和

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[N];
int b[N];
int sum[N];
string s;
int m;
void solve() {
   
	cin>>s;
	int n=s.size();
	s=' '+s;
	cin>>m;
	for(int i=0;i<m;i++){
   
		int x;
		cin>>x;
		a[i]=x;
		if(x>n/2) x=n-x+1;
		b[x]++;
	}
	for(int i=1;i<=n/2;i++) sum[i]=sum[i-1]+b[i];
	for(int i=1;i<=n/2;i++){
   
		if(sum[i]%2==1) swap(s[i],s[n-i+1]);
	}
	for(int i=1;i<=n;i++) cout<<s[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;
}

46.C. Theofanis’ Nightmare

贪心

乘i看作加i次

考虑从前往后,什么时候断开,当后缀和大于0那么就断开,如果后缀和小于0断开是不优的,因为放在后面,让负的多加了

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[N];
int last[N];
int n;
void solve() {
   
	cin>>n;
	memset(last,0,sizeof last);
	for(int i=1;i<=n;i++) cin>>a[i];
	last[n]=a[n];
	for(int i=n-1;i>=1;i--) last[i]=a[i]+last[i+1];
	vector<int>ans;
	int sum=a[1];
	for(int i=2;i<=n;i++){
   
		if(last[i]>0){
   
			ans.push_back(sum);
			sum=a[i];
		}
		else sum+=a[i];
	}
	if(sum) ans.push_back(sum);
	int res=0;
	for(int i=0;i<(int)ans.size();i++){
   
		res+=(i+1)*ans[i];
	}
	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;
}

47.C. Diverse Matrix

构造大小最小的多样性矩阵,无解输出0

1行1列无解

特判行为1和列为1的情况

发现一种构造方式:第一行放r+c的后c个数,然后第i行是第1行的i倍

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int r,c;
void solve() {
   
	cin>>r>>c;
	if(r==1&&c==1){
   
		cout<<0<<endl;
		return;
	}
	if(r==1){
   
		for(int i=2;i<=c+1;i++) cout<<i<<' ';
		cout<<endl;
		return;
	}
	if(c==1){
   
		for(int i=2;i<=r+1;i++) cout<<i<<endl;
		return;
	}
	int m=r+c;
	int st=m-c+1;
	for(int i=1;i<=r;i++){
   
		for(int j=st;j<=m;j++){
   
			cout<<i*j<<' ';
		}
		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;
}

48.C. Complementary XOR

长度为n的01串a和b
一次操作:
选择l,r
1.串a[l,r]01反转
2.串b[1,l-1]01反转或者[r+1,n]01反转,如果存在就必须01反转
使得两个串全部都变成0,最多操作n+5次,无解输出NO

感觉这个操作限制太大,某种序列才能成功全部变成0
串b,要么和a相等,要么和a相反才可以
每次操作后,串a和串b要么完全相同,要么完全相反
那么就把串a先全部变成0,如果串b已经全部为0了,那么结束,如果串b全为1,那么(1,1),(2,n),(1,n)

trick:

如果整个序列并不是一段一段连续的,而是交替的,那么对一段进行操作不好操作,考虑对单个进行操作

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int, int>PII;
int n;
string a, b;
void solve() {
   
	cin >> n;
	cin >> a >> b;
	string c = a;
	for (int i = 0; i < n; i++) {
   
		if (c[i] == '1') c[i] = '0';
		else c[i] = '1';
	}
	if (b != a && b != c) {
   
		cout << "NO" << endl;
		return;
	}
	bool ok=false;
	if(a[0]=='1') ok=true;
	vector<PII>ans;
	for(int i=0;i<n;i++){
   
		if(a[i]=='1') ans.push_back({
   i+1,i+1});
	}
	int cnt=ans.size();
	if(ok) cnt--;
	if(cnt%2){
   
		if(b[0]=='0') b[0]='1';
		else b[0]='0';
	}
	if(b[0]=='1'){
   
		ans.push_back({
   1,1});
		ans.push_back({
   2,n});
		ans.push_back({
   1,n});
	}
	cout<<"YES"<<endl;
	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;
}

49.C. Set Construction

n * n的01矩阵
bij为第i行第j列

构造A1,A2,…An,满足如果Ai是Aj的适当子集,那么bij=1,否则bij=0
一定有解

A2->A1->A4
A3->A4
感觉有点像拓扑,但具体感觉又不会
可以拓扑,首先让点i初始都只有i,然后拓扑排序,子集中有的,全都给它的超集

trick:

很妙,让点i初始都只有i,当作案例

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1010;
char b[N][N];
int d[N];
int n;
set<int>s[N];
void solve() {
   
	cin>>n;
	memset(d,0,sizeof d);
	vector<vector<int>>e(n+1);
	for(int i=1;i<=n;i++){
   
		for(int j=1;j<=n;j++){
   
			cin>>b[i][j];
			if(b[i][j]=='1'){
   
				e[i].push_back(j);
				d[j]++;
			} 
		}
	}
	for(int i=1;i<=n;i++) s[i].clear();
	for(int i=1;i<=n;i++) s[i].insert(i);
	queue<int>q;
	for(int i=1;i<=n;i++){
   
		if(!d[i]) q.push(i);
	}
	while(q.size()){
   
		int t=q.front();
		q.pop();
		for(auto v:e[t]){
   //t为子集,v为超集
			for(auto x:s[t]) s[v].insert(x);
			d[v]--;
			if(!d[v]) q.push(v);
		}
	}
	for(int i=1;i<=n;i++){
   
		cout<<s[i].size()<<' ';
		for(auto v:s[i]) 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;
}

50.B. Arrays Sum

第一次我们选择前k段,之后每一次都要先选前面的若干个0,这样一次就只能选k−1段。

trick:

将一个数分解x为若干个数的和,构造题的话随我们自己构造,我们可以分解为一个x,然后其它全补0,此时想补几个0就补几个0

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=110;
int a[N];
int n,k;
void solve() {
   
	cin>>n>>k;
	for(int i=1;i<=n;i++) cin>>a[i];
	int cnt=1;//一共有cnt种数字
	for(int i=2;i<=n;i++){
   
		if(a[i]!=a[i-1]) cnt++;
	}
	if(k==1&&cnt!=1) cout<<-1<<endl;
	else if(k==1) cout<<1<<endl;
	else{
   
		int ans=0;
		if(cnt>=k) cnt-=k,ans++;
		ans+=cnt/(k-1)+(cnt%(k-1)!=0);
		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;
}

相关推荐

  1. codeforces 1200E

    2024-02-11 19:54:02       36 阅读
  2. 华为OD-C卷-披萨[100]

    2024-02-11 19:54:02       12 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-02-11 19:54:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-11 19:54:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-11 19:54:02       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-11 19:54:02       20 阅读

热门阅读

  1. 关于LLaMA Tokenizer的一些坑...

    2024-02-11 19:54:02       31 阅读
  2. 蓝桥杯2023年第十四届省赛真题----棋盘

    2024-02-11 19:54:02       38 阅读
  3. 【Linux】Ubuntu 22.04 升级 nodejs 到 v18

    2024-02-11 19:54:02       29 阅读
  4. fpga 需要掌握哪些基础知识?

    2024-02-11 19:54:02       27 阅读
  5. 修改GI文件的权限

    2024-02-11 19:54:02       32 阅读
  6. python字串节对象Bytes

    2024-02-11 19:54:02       21 阅读
  7. CM3035 Advanced Web Development

    2024-02-11 19:54:02       24 阅读