codeforces 1300分

文章目录

1.B. Random Teams

n个人,分成m组,每个组至少分一个人

靠一些直觉,分别对最大和最小进行思考
最大:除一组外其它全为1
最小:平均分,那么怎样平均分呢?

trick:平均分的做法:先每组分配n/m,如果n%m不为0的话,那么取n%m个加1

主要是对平均分这一技巧的掌握

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,m;
void solve() {
	cin>>n>>m;
	int t=n-m+1;
	int maxn=t*(t-1)/2;
	int x=n/m;
	int y=n%m;
	int minn=(x+1)*x/2*y+x*(x-1)/2*(m-y);
	cout<<minn<<' '<<maxn<<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. Anti-Sudoku

9 * 9的数独,最多改掉9个数,使得每行,每列,每个3 * 3都至少有两个相同的元素(题目说一定有解)

直觉是修改的数值每行每列都不一样,比如(1,1),(2,2),…(9,9)通过样例来验证,是适用的
那么具体改成什么值呢?由于原本这是一个数独,即每行1到9不重复,没列1到9不重复,3 * 3中1到9不重复,所以只要改成不一样的就会造成它所在行,列,3 * 3会出现至少两个相同的元素,发现这样不行,还得再改改,这样的话很多3 * 3还是没有改掉,所以还要考虑3 * 3

trick:直接对以下进行修改
(1,1) (2,4)(3,7)
(4,2) (5,5)(6,8)
(7,3) (8,6)(9,9)

当然,这里也有一定的技巧,就是横纵坐标分别放到dx数组和dy数组里,直接遍历即可,而不是写9个if

#include<bits/stdc++.h>
#define endl '\n'
//#define int long long
using namespace std;
char a[10][10];
int dx[9]={1,2,3,4,5,6,7,8,9};
int dy[9]={1,4,7,2,5,8,3,6,9};
void solve() {
	for(int i=1;i<=9;i++){
		for(int j=1;j<=9;j++){
			cin>>a[i][j];
		}
	}
	for(int i=0;i<9;i++){
		int x=dx[i],y=dy[i];
		if(a[x][y]=='9') a[x][y]='1';
		else a[x][y]=(char)(a[x][y]+1);
	}
	for(int i=1;i<=9;i++){
		for(int j=1;j<=9;j++){
			cout<<a[i][j];
		}
		cout<<endl;
	}
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

3.B. Trouble Sort

n个整数
ai表示值,bi表示类型
操作:选择两个类型不同的数,交换它们
操作不限次数,使得非降序,问是否可以

只要分别将两种类型的升序,然后合并在一起,再检验是否非降序
难点在于怎么实现:其实很简单,就是先把两种类型的数分别放在不同的multiset里,然后依次按照类型赋值回去即可
由于multiset中的erase是删掉的是与之相同的所有元素,我要删第一个,不知道怎么删,所以改成小根堆

样例一直错,然后才发现题目读错了,全都白分析了,不对,题目读对了,但是在分析的时候错乱了,刚好把题意记反了,记成交换相同类型的了

这告诉我们一个道理:先别急着敲代码,先看看想出的方法是不是对样例适用,应该至少保证样例全都适用

后面想的是进行一个类似于冒泡排序,就是如果类型不同且大小逆序就互换,但是时间复杂度肯定不允许

trick:操作是对某个数进行交换且操作次数不限,那么看两两交换是否可以扩展到更多数的交换,这里只要有0和1的交错,那么就全部都可以随意交换

#include<bits/stdc++.h>
#define endl '\n'
//#define int long long
using namespace std;
const int N=1e5+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];
	bool flag=true;
	int cnt_0=0,cnt_1=0;
	if(b[1]==0) cnt_0++;
	else cnt_1++;
	for(int i=2;i<=n;i++){
		if(a[i-1]>a[i]) flag=false;
		if(b[i]==0) cnt_0++;
		else cnt_1++;
	}
	if(flag){
		cout<<"Yes"<<endl;
		return;
	}
	if(cnt_0==0||cnt_1==0) cout<<"No"<<endl;
	else cout<<"Yes"<<endl;
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

4.Problem - 1401C - Codeforces

长度为n的数组

ai为正整数

操作:选择两个数,如果它们的最大公因数等于整个数组最小的那个就可以交换
操作次数不限

要求使a非降序

想想是否可以扩展一下交换
数组中最小的数在数组给定之后就已经确定下来了

至此都没什么解题的突破口

trick:

这类题都是有一般套路的

这类题的特征:操作是满足某性质的两个数可以交换,操作次数不限,目标是排好序

思考方向为是否有中间量可以作为交换的媒介,两数通过中间的媒介进行交换,某个数如果和中间量有联系的话,那么就可以和其它任意和中间量有联系的进行交换,从而达到自由移动

另外一种思考的方向是,我们要的结果是确定的,所以先排个序,结果确定下来,然后看位置不正确的数是否全部都可以自由移动

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[N],b[N];
int n;
int gcd(int a,int b){
	if(b==0) return a;
	return gcd(b,a%b);
}
void solve() {
	cin>>n;
	int minn=2e9;
	for(int i=1;i<=n;i++) cin>>a[i],b[i]=a[i],minn=min(minn,a[i]);
	sort(b+1,b+1+n);
	for(int i=1;i<=n;i++){
		if(a[i]!=b[i]){
			if(gcd(a[i],minn)!=minn){
				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;
}

5.Problem - 1367C - Codeforces

1到n张桌子
长度为n的01字符串,1代表餐桌有人,0代表没人
保证给定字符串中任意两个1相差距离大于k
然后问最多将几个0变成1使得任意两个1相差距离仍然大于k

如果当前字符为0的话,查找往后k个距离内是否有1,如果没有就可以将该位置的0变为1,并跳到i+k的位置

如果当前字符为1的话,那么直接跳到i+k的位置

trick:

当出现最的时候,一种思路就是贪心:最好是怎么样,实在没办法才舍弃

贪心其中一种方向就是从前往后遍历,一边遍历一边贪,若可以将该位置的0变成1则变,尽可能多

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N = 2e5 + 10;
int a[N];
int n, k;
string s;
void solve() {
	cin >> n >> k;
	cin >> s;
	vector<int>pos;//存放1的位置
	for (int i = 0; i < n; i++) {
		if (s[i] == '1') pos.push_back(i);
	}
	int ans = 0;
	for (int i = 0; i < (int)s.size(); i++) {
		if (s[i] == '0') {
			int pos1 = upper_bound(pos.begin(), pos.end(), i) - pos.begin();
			if (pos1 >= 0 && pos1 < (int)pos.size()) {
				if(pos[pos1] - i > k){
					ans++;
					i = i + k;
				}
			}
			else{
				ans++;
				i=i+k;
			}
		} else {
			i = i + k;
		}
	}
	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;
}

6.C. Coin Rows

矩阵
共m列,2行
aij表示硬币数量

博弈
起点为(1,1),终点为(2,m)
操作:往右移动一格或者往下移动一格

最终分数为Bob获取的硬币数量
Alice从起点走到终点,拿取硬币
Alice想使得分数最小
Bob再走,想使分数最大

两者独立,互不相关

有关于数学数字,归结为数学敏感,手玩,找规律

0c6358664849d7041ff52f6808dd4bd5

trick:

1.有关于数学数字,归结为数学敏感,手玩,找规律

2.不要乱加特判,情况虽然简单,但不要也开始就加上,先看一般情况能否覆盖特殊情况,如果可以覆盖,那么它就不是特殊情况,加了特判反而容易错

这边对m=2时特判反而错了,属于画蛇添足了

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[3][N];
int last[N],pre[N];
int m;
void solve() {
	cin>>m;
	for(int i=1;i<=2;i++){
		for(int j=1;j<=m;j++){
			cin>>a[i][j];
		}
	}
	//特判
	if(m==1){
		cout<<0<<endl;
		return;
	}
	//预处理第一行的后缀和
	last[m]=a[1][m];
	for(int i=m-1;i>=1;i--) last[i]=last[i+1]+a[1][i];
	//预处理第二行的前缀和
	pre[1]=a[2][1];
	for(int i=2;i<=m;i++) pre[i]=pre[i-1]+a[2][i];
	//遍历
	int ans=2e9;
	ans=min(ans,pre[m-1]);
	ans=min(ans,last[2]);
	for(int i=1;i<=m-2;i++){//i为第二行前缀的长度
		int len=m-i-1;//第一行后缀的长度
		ans=min(ans,max(pre[i],last[m+1-len]));
	}
	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;
}

7.B. Nastia and a Good Array

长度为n的数组a,均为正整数

good数组:所有相邻两个数最大公因数均为1
操作:选择任意两个数,将其中一个数变成小的那个数min,另一个数随便变成一个大于等于min的数
最多操作n次,可以证明一定可以实现,变成good数组
输出操作

有一个很重要的性质:相差为1的两个数的最大公因数肯定为1,这在之前做题的时候做到过
把相邻的两个数变成相差为1的两个数,n次绝对够了,而且是刚好n-1次,想错了,没有想到修改后面就把前面的数给改了
为了避免修改前面的数会把后面的数给改掉,需要按升序的顺序来,这样ok了,这样还是不对,因为虽然排序了,但是原本的顺序是没有打乱的
而升序还有一个原因----该题与顺序无关,先排个序

这题最小的数可以保留,然后其它都可以变成大于等于最小数的任何数,那么可以奇数位全放最小数,偶数位全放最小数+1,或者偶数位全放最小数,奇数位全放最小数+1

trick:

1.相差为1的两个数的最大公因数肯定为1,这是之前做题做到过的,一个很重要很关键的性质,但是这里还要再拓展一下,题目要求是任意相邻两个数的最大公因数均为1,可能会想到每次都加1,呈现一个升序的状态,但是也可以减1,加1,这样就分为奇数位和偶数位,奇数位均相同,偶数位均相同,奇数位上的数和偶数位上的数差1

2.将一个数和另一个数联系起来,输出的时候输出这两个数,包括上次做到的输出生成树一条边的两个端点,看是否可以其中一个数就固定下来

3。挖掘操作(动作)的性质,想想哪些是一定的,首先做什么是一定的,在这里是最小的那个数一定是可以保留的

#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];
	int pos=1,minn=a[1];
	for(int i=2;i<=n;i++){
		if(a[i]<minn){
			minn=a[i];
			pos=i;
		}
	}
	cout<<n-1<<endl;
	if(pos%2==1){
		for(int i=1;i<=n;i++){
			if(pos==i) continue;
			if(i%2==0) cout<<pos<<' '<<i<<' '<<a[pos]<<' '<<a[pos]+1<<endl;
			else cout<<pos<<' '<<i<<' '<<a[pos]<<' '<<a[pos]<<endl;
		}
	}
	else{
		for(int i=1;i<=n;i++){
			if(pos==i) continue;
			if(i%2==0) cout<<pos<<' '<<i<<' '<<a[pos]<<' '<<a[pos]<<endl;
			else cout<<pos<<' '<<i<<' '<<a[pos]<<' '<<a[pos]+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;
}

8.A1. Prefix Flip (Easy Version)

长度为n的数组a和数组b,01串
操作:取长度为len的前缀,将0变成1,将1变成0,然后翻转
将字符串a变成b,需操作几次(最多3*n次,一定有解)

3次操作:翻转前i个字符,翻转第1个字符,再翻转前i个字符,可以将第i个字符进行翻转–这个规律真的很难发现,通过手玩也很难,可以记一下这个规律

trick:

1.一定有解,最多3*n次–>肯定可以构造出一种万能的通用的解,因为最多n个字符,然后通过3可以猜测每3次就可以将一个字符翻转

2.非常通用的一种方法–手玩

#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;
	vector<int>ans;
	for(int i=0;i<n;i++){
		if(a[i]!=b[i]){
			ans.push_back(i+1);
			ans.push_back(1);
			ans.push_back(i+1);
		}
	}
	cout<<ans.size()<<' ';
	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;
}

9.D. Number into Sequence

构造序列,每个数大于1,所有数乘积为n,后一个数是前一个数的倍数,长度要最大(一定有解)

分解质因数,将出现次数最多的质因数(假设k个)输出k-1个,剩下一个和其它乘积

trick:

数学知识:每个合数都可以写成几个质数相乘的形式(唯一),其中每个质数都是这个合数的因数,把一个合数用质因数相乘的形式表示出来,叫做分解质因数,这样得到的分解因数的序列一定是最长的,因为质数不能再分解了,然后将质因数乘积合成一个因数,这样可以有办法满足题意得最长序列

分解质因数的模板:试除法

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
	cin>>n;
	int m=n;
	vector<int>ans;
	map<int,int>mp;
	for(int i=2;i<=n/i;i++){
		if(n%i==0){
			ans.push_back(i);
			while(n%i==0) {
				n/=i;
				mp[i]++;
			}
		}
	}
	if(n>1){
		ans.push_back(n);
		mp[n]++;
	}
//	for(int i=0;i<(int)ans.size();i++) cout<<ans[i]<<' '<<mp[ans[i]]<<endl;
	int maxn=mp[ans[0]];
	int maxi=ans[0];
	for(int i=1;i<(int)ans.size();i++){
		if(maxn<mp[ans[i]]){
			maxn=mp[ans[i]];
			maxi=ans[i];
		}
	}
	cout<<maxn<<endl;
	for(int i=0;i<maxn-1;i++) cout<<maxi<<' ';
	cout<<m/(int)pow(maxi,maxn-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;
}

10.C1. Pokémon Army (easy version)

n个神奇宝贝
ai表示神奇宝贝的实力(均不同)

按顺序挑选神奇宝贝,加减交替,得到该支军队的实力,求出最大兵力

开头和结尾肯定是要加的(让个数为奇数,不然后面减一个肯定变小)
通过手玩造样例发现,比如8 7 6 5 4,如果选择8,7,6,5,10,那么最终结果是8-7+6-5+10=12,而选择8,5,10,则为8-5+10=13,所以一加一减选择一个最大的和最小的,它其实就是一个波峰波谷,每次都选择波峰和波谷即可

trick:
1.手玩–造样例

2.找波峰和波谷

波峰:a[i] > a[i - 1] && a[i] > a[i + 1]

波谷:a[i] < a[i - 1] && a[i] < a[i + 1]

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N = 3e5 + 10;
int a[N];
int n, q;
void solve() {
	cin >> n >> q;
	for (int i = 1; i <= n; i++) cin >> a[i];
	//找波峰和波谷
	bool flag = 1; //1表示要找波峰,0表示要找波谷
	vector<int>ans;
	if (a[1] > a[2]) ans.push_back(a[1]), flag = 0;
	for (int i = 2; i <= n - 1; i++) {
		if (flag == 1) { //找波峰
			if (a[i] > a[i - 1] && a[i] > a[i + 1]) ans.push_back(a[i]), flag = 0;
		} else if (flag == 0) { //找波谷
			if (a[i] < a[i - 1] && a[i] < a[i + 1]) ans.push_back(a[i]), flag = 1;
		}
	}
	if (a[n] > a[n - 1]) ans.push_back(a[n]);
	int res = 0;
	for (int i = 0; i < (int)ans.size() - 1; i += 2) {
		res += ans[i] - ans[i + 1];
	}
	res += ans[ans.size() - 1];
	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;
}

11.C. Closest Cities

长度为n的数组a,数的范围为[0,1e9]
共m行询问,每次给定两个整数x和y,计算从城市x到城市y需要花费最少的硬币数
共m座城市,位于ai,按升序排列
对于一座城市,离它最近的城市是唯一的

操作:从城市x到y,花费它们距离的金币或者到离x最近的城市(唯一的)花费1金币

x和y在两个方向,x朝着y方向走,一直走最近,直到最近的是往回走,千万不能走回头路,此时直接花费距离金币到y
并查集不可行,并查集不能有环,那就用两个并查集,往右的和往左的,但是这样是错的,因为并不是走到并查集的头就断了,可以先利用城市距离过去再继续走最近城市

由于不能走回头路,所以我们利用一个前缀和以及后缀和就可以了,如果可以走最近城市那么就加1,否则就加两城市之间的距离
不能走回头路,然后如果能走最近城市就走最近城市,如果不能走就花费距离金币,这样花费代价是最小的

trick:

1.多次询问区间以及不能走回头路,考虑方向为前缀和

2.并查集是不能出现环的

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[N];
int pre[N],last[N];
int n,m;
void solve() {
	cin>>n;
	memset(pre,0,sizeof pre);
	memset(last,0,sizeof last);
	for(int i=1;i<=n;i++) cin>>a[i];
	//前缀和
	for(int i=1;i<n;i++){
		if(i==1) pre[i+1]=pre[i]+1;
		else{
			if(abs(a[i]-a[i+1])<abs(a[i]-a[i-1])) pre[i+1]=pre[i]+1;
			else pre[i+1]=pre[i]+abs(a[i]-a[i+1]);
		}
	}
	//后缀和
	for(int i=n;i>1;i--){
		if(i==n) last[i-1]=last[i]+1;
		else{
			if(abs(a[i]-a[i-1])<abs(a[i]-a[i+1])) last[i-1]=last[i]+1;
			else last[i-1]=last[i]+abs(a[i]-a[i-1]);
		}
	}
	cin>>m;
	while(m--){
		int x,y;
		cin>>x>>y;
		if(x<y) cout<<pre[y]-pre[x]<<endl;
		else cout<<last[y]-last[x]<<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.B. Road Construction

一共有n座城市
建设道路(无向图),对于一座城市,到任何其它城市最多穿越两条道路
m对城市不能建设道路
一定有解
输出最少建设多少条道路,输出具体建设情况

记录不能建设道路的情况,记录每个城市不能用到的次数,次数最少的x去匹配那些可以和它连的,对于不能和x连的,看是否可以连到x的一级分支上
数据比较小,可以选择暴力

trick:

1.当两者需要联系起来时输出,往往其中一个可以定死,这是之前总结过的,在这里是解题的关键

2.数据比较小,直接暴力

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1010;
int n,m;
bool st[N][N];
void solve() {
	cin>>n>>m;
	map<int,int>mp;
	memset(st,false,sizeof st);
	for(int i=0;i<m;i++){
		int x,y;
		cin>>x>>y;
		st[x][y]=st[y][x]=true;
		mp[x]++;
		mp[y]++;
	}
	int minn=2e9,mini=-1;
	for(int i=1;i<=n;i++){
		if(minn>mp[i]){
			minn=mp[i];
			mini=i;
		}
	}
	cout<<n-1<<endl;
	vector<int>ans;
	vector<int>res;
	for(int i=1;i<=n;i++){
		if(i==mini) continue;
		if(!st[mini][i]) cout<<mini<<' '<<i<<endl,res.push_back(i);
		else ans.push_back(i);
	}
	for(int i=0;i<(int)ans.size();i++){//枚举和mini不能相连的
		for(int j=0;j<(int)res.size();j++){
			if(!st[ans[i]][ans[j]]) cout<<ans[i]<<' '<<ans[j]<<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. Di-visible Confusion

长度为n的数组a(数的范围[1,1e9])
操作:选择一个索引i,保证ai不是i+1的倍数,删掉ai
直到序列为空、
问是否可以删除整个序列

手玩
要想删掉第一个数,那么第一个数就不能是2的倍数(第一个数要看的索引是固定的,是2)
如果全部是奇数,那么就一直删第一个数
删掉后面不会影响前面,删掉前面会影响后面
先把能删的都删掉,然后剩下的就都是不能删的了
然后只要把第一个删掉,所有都错一位,这样全部都变成能删的了,然后从后往前一个一个删
综上所述,如果第一个数是奇数的话, 那么Yes,否则N
全错了,这样错位下来不一定,比如说30,既是5的倍数,又是6的倍数

该题的做法是假设第i个数前面的数都可以被删掉,那么第i个数只要在2到i+1中有一个不是因数,也可以被删掉,如果有一个数,2到i+1全部都是它的因数的话,那么它不可能被删掉

由于2乘以3,一直乘到大概23就超过1e9了,所以最多判断22个数,这样双重循环是不会超时的

trick:

1.整除想到因子

2.对于删数问题,肯定是一位一位删,不可能一下子全部都删掉,其中一个思考的方向是假设前面的数全部都可以删掉,然后如何删掉该数

3.如果x是很多数的倍数,那么x也是它们最小公倍数的倍数,判断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;
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	bool flag=true;
	for(int i=1;i<=n;i++){
		bool ok=false;
		for(int j=i+1;j>=2;j--){
			if(a[i]%j!=0){
				ok=true;
				break;
			}
		}
		flag&=ok;
	}
	if(flag) 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;
}

14.C. Matching Numbers

给定n
然后让1到2*n两两匹配,使得它们的和按顺序分别是加1

它们所有的和是固定的,然后每个数都差1,求和就是等差数列,等差数列一共n个数

可以列出一个式子,假设第一个和为x,那么(x+x+n-1) * n /2=(1+2 * n)* 2 * n /2

得到x=(3 * n+3)/2

然后n个和即为,x,x+1,…x+n-1

通过手玩发现构造规律:将1到2n分成两半,左半边的x-(n+1)到n分别按次序和右半边相加,然后右半边剩下的按次序和左半半剩下的相加即可

trick:

突破口在于和是固定的,一旦找到某个是不变的,它就是题目的突破口

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
	cin>>n;
	if((3*n+3)%2){
		cout<<"No"<<endl;
		return;
	}
	int x=(3*n+3)/2;//第一个和
	int pos=x-(n+1);
	cout<<"Yes"<<endl;
	int idx;
	for(int i=n+1,j=pos;j<=n;i++,j++){
		cout<<i<<' '<<j<<endl;
		idx=i;
	}
	for(int i=idx+1,j=1;i<=2*n;i++,j++){
		cout<<i<<' '<<j<<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. Co-growing Sequence

长度为n的数组x(数的范围[0,1e9])
增长序列:任意i,ai & ai+1 =ai(n为1也是增长序列)

构造序列b,ai^bi=ci,使得c是增长序列(这里ai并不是增长序列)
求b的字典序最小

b 1 b_1 b1可以任意,直接取为最小,为0

即( a i a_i ai$b_i$)&($a_{i+1}$ b i + 1 b_{i+1} bi+1)= a i + 1 a_{i+1} ai+1^ b i + 1 b_{i+1} bi+1

得到 a i a_i ai$b_i$中1所在的位,$a_{i+1}$ b i + 1 b_{i+1} bi+1也都是1,由于 a i + 1 a_{i+1} ai+1已知,那么只需要把 a i + 1 a_{i+1} ai+1中应该为1的位但不是1的改成1即可作为b,其它位都为0,这样必要的1有了,其它都为0,字典序最小

trick:

1.按位异或,一个重要的性质是,相同为0,即aa=0,从而得到abb=a,即a=abb,如果题目要求构造一个数使得该数和给定数a异或起来满足要求,可以先把满足要求的数x确定下来,然后需要构造的数y=xa(因为ay=x,所以aya=xa)

如果两者异或起来为1的话,那么两者不同,由于二进制只有0和1,所以两者其中一个是0,另一个是1,换句话说,两者刚好相反,互补

2.按位与,如果a & b =a,那么a中1所在的位,b也都是1

3.按位或,如果a中为1的位,b都想要为1,同时保留b中原有的1,那么b或上a,相当于将两者为1的位都并起来为x,如果p^q为x的话,除了两者都为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];
	b[1]=0;
	for(int i=2;i<=n;i++){
		int x=a[i-1]^b[i-1];
		b[i]=(x|a[i])^a[i];///a[i]^b[i]=x|a[i]
	}
	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;
}

16.B. Simple Game

在1到n中选择一个数
两个人M和A博弈,对于随机整数c,谁选的数离c近,谁就赢,如果距离相同,M赢

M数字已知,要想让A赢的概率最大,A应该选择哪个数

根据样例,看M在左半边还是右半边
选择数字M-1或者M+1
M-1和n-(M+1)+1=n-M比,哪个大选哪个

坑点:

M-1和M+1超出选数的范围了

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,m;
void solve() {
	cin>>n>>m;
	if(m-1>=n-m&&m-1>=1) cout<<m-1<<endl;
	else if(m-1<n-m&&m+1<=n) cout<<m+1<<endl;
	else cout<<m<<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.B. Swap and Delete

1000分的题,但还是归到这来,因为这题和之前做过的题联系起来了,比较有重大意义

给定一个01串s
操作:删除一个字符,花费1金币或者免费交换一对字符
次数不限

使得ti不等于si对于整个t(可以为空)
问最少花费多少金币

两个一样的序列进行0和1的匹配,分别统计0和1的数量,从头开始遍历,一直到不能匹配

trick:

1.两两可以任意交换–可以随便排序

2.两两匹配问题

分为三种

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

第二种是两个不同的序列进行匹配,比如说每次匹配差值最大的,要么序列a的最大和序列b的最小匹配,要么序列a的最小和序列b的最大匹配

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

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
string s;
void solve() {
	cin>>s;
	int n=s.size();
	map<char,int>mp;
	for(int i=0;i<n;i++) mp[s[i]]++;
	for(int i=0;i<(int)s.size();i++){
		if(s[i]=='1'){
			if(mp['0']) mp['0']--;
			else{
				cout<<n-i<<endl;
				return;
			}
		}
		else{
			if(mp['1']) mp['1']--;
			else{
				cout<<n-i<<endl;
				return;
			}
		}
	}
	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;
}

18.C. Game with Multiset

add x表示在集合中添加一个2的x次方
get w问是否可以从集合中取得一些数,和为w

trick:
任何一个数都可以由若干个2的次幂组成

有两种方法:

1.将该数转化成二进制,然后一位一位看,如果第x位为1的话,那么 就有 2 x 2^x 2x,具体操作方法可以参考快速幂

int cnt=0;
while(x){
if(x&1) ans.push_back(cnt); 
x>>=1;
cnt++;
}

2.从高到低贪心

for(int i=29;i>=0;i--){
if(x>=(1ll<<i)){
  ans.push_back(i);
  x-=(1ll<<i);
}
}

但是你会发现,每一位二进制数都只用一次,该题中每个2的次幂不止一个,实际上从大到小贪心和每个2的次幂只用一次的贪心本质上是一样的

如果某个2的次幂有多个的话,那么就只保留一个(有奇数个)或者 一个也不保留(有偶数个),然后其余的进行合成,往上进位

比如说有5个 2 3 2^3 23,想当于1个 2 5 2^5 25

再比如说6个 2 3 2^3 23,相当于1个 2 5 2^5 25以及1个 2 4 2^4 24

所以总能转化成每个2的次幂只用一次

所以从高到低进行贪心,如果能构成就能,不能就不能

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int m;
void solve() {
	cin>>m;
	map<int,int>mp;
	while(m--){
		int op,x;
		cin>>op>>x;
		if(op==1) mp[x]++;
		else{
			for(int i=29;i>=0;i--){
				if(mp[i]){
					x-=min(x/(1ll<<i),mp[i])*(1ll<<i);
				}
			}
			if(x==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;
}

19.D. Ice Cream Balls

n表示想要制作多少种类型的冰淇淋(exactly)
两个球做成一个冰淇淋
问最少需要买几个球(答案总是存在)

之前做过了

二分求出C(x,2)小于等于n,x为不同类型球的数量,然后剩下的补足买之前已经买过的

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
int C2(int x){
	return x*(x-1)/2;
}
void solve() {
	cin>>n;
	int l=1,r=1e10;
	while(l<r){
		int mid=(l+r+1)/2;
		if(C2(mid)>n) r=mid-1;
		else l=mid;
	}
	if(C2(l)==n) cout<<l<<endl;
	else cout<<l+n-C2(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;
}

20.A. Knapsack

共n个物品,权重为wi
背包容量为W

其实审题这一步很关键,最有用的办法就是一边读题一边用自己的话记录,一边理解一边想

将物品放入背包,使得重量大于等于背包容量的一半,输出一种方案,无解输出-1

发现贪心可行
从大往小贪,只要可以放就放,一旦超过了总容量的一半就可以结束了,如果直到最后都没有超过总容量的一半,就无解
证明:从大到小遍历,首先如果第一个就超过总容量的一半,那么直接结束,否则,就看下一个,由于大的都没有超过总容量的一半,再加一个小的一定不会超过总容量,假设加起来超过总容量的一半,那么直接结束,否则同理,再往下加也不会超过总容量的

trick:

1.其实审题这一步很关键,最有用的办法就是一边读题一边用自己的话记录,一边理解一边想

2.当脑子里立马蹦出个思路,特别是贪心,一定要造不同情况的复杂的极端的样例进行验证

3.如果排序后仍需要知道下标,可以将pair类型存入vector,这比结构体方便

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
int n,w;
void solve() {
	cin>>n>>w;
	vector<PII>ans;
	for(int i=1;i<=n;i++){
		int x;
		cin>>x;
		ans.push_back({x,i});
	}
	sort(ans.begin(),ans.end());
	reverse(ans.begin(),ans.end());
	int sum=0;
	vector<int>res;
	for(int i=0;i<(int)ans.size();i++){
		if(w>=ans[i].first){
			sum+=ans[i].first;
			res.push_back(ans[i].second);
			if(sum>=(w+1)/2){
				cout<<res.size()<<endl;
				for(auto v:res) cout<<v<<' ';
				cout<<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;
}

21.C. Parity Shuffle Sorting

长度为n的数组a(数[0,1e9])
操作:选择两个数,如果两个数的和为奇数,那么让后一个数等于前一个数,如果和为偶数,那么让前一个数等于后一个数
最多n次–>尽可能都用完
使序列非降序,一定有解–>万能通用的方法

操作的对象为两个,可以让一个定死,也可以让两个都定死
有一个不变的点就是里面的数不能凭空出现里面没出现过的数
如果第一个数是偶数的话,那么先让所有偶数都变相同,然后所有的奇数都变得和第一个数一样
如果第一个数是奇数的话,同理

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=1e5+10;
int a[N];
int n;
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	vector<PII>ans;
	if(a[1]%2==0){
		int idx=-1;
		for(int i=n;i>=1;i--){
			if(a[i]%2==0){
				idx=i;
				break;
			}
		}
		if(idx!=1){
			for(int i=1;i<=n;i++){
				if(i==idx) break;
				if(a[i]%2==0) ans.push_back({i,idx});
			}
		}
		for(int i=2;i<=n;i++){
			if(a[i]%2) ans.push_back({1,i});
		}
	}
	else{
		int idx=-1;
		for(int i=n;i>=1;i--){
			if(a[i]%2){
				idx=i;
				break;
			}
		}
		if(idx!=1){
			for(int i=1;i<=n;i++){
				if(i==idx) break;
				if(a[i]%2) ans.push_back({i,idx});
			}
		}
		for(int i=2;i<=n;i++){
			if(a[i]%2==0) ans.push_back({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;
}

22.A1. Make Nonzero Sum (easy version)

长度为n的数组a(均为正负1)

分段,每个分段的计数方法为加减交替,第一个数为加
满足所有分段加起来为0

如果n为奇数,无解
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];
	if(n%2==1){
		cout<<-1<<endl;
		return;
	}
	vector<PII>ans;
	for(int i=1;i<=n;i+=2){
		if(a[i]==a[i+1]) ans.push_back({i,i+1});
		else ans.push_back({i,i}),ans.push_back({i+1,i+1});
	}
	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;
}

23.C. Elemental Decompress

长度为n的数组a(数[1,2e5])

构造两个全排列,使得max(pi,qi)=ai,可能无解

ai并不是全排列
ai确定之后,那么pi和qi都必须小于等于ai,然后其中一个等于ai,另一个小于等于ai

其中一个等于ai后,如果后缀中没有ai了,那么另一个也可以等于ai

首先,将p中还没放置的数都放在set1中,将q中没放置的数都放在set2中,如果当前需要的数不在set1中,那么就需要q来放,如果当前的数在后面没有出现过,那么就p和q放一样的数
最后没有放的位置进行补数,当然得放比ai小的数,可以利用set自带的二分,最后检验

trick:

利用set的二分时,返回迭代器,进行迭代器的加减时特别注意是否越界

#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],q[N];
int n;
void solve() {
	cin>>n;
	memset(p,0,sizeof p);
	memset(q,0,sizeof q);
	map<int,int>mp,mmp;
	for(int i=1;i<=n;i++) cin>>a[i],mp[a[i]]++;
	set<int>s1,s2;
	for(int i=1;i<=n;i++) s1.insert(i),s2.insert(i);
	for(int i=1;i<=n;i++){
		mmp[a[i]]++;
		if(s1.count(a[i])){
			p[i]=a[i];
			s1.erase(a[i]);
			if(mmp[a[i]]==mp[a[i]]&&s2.count(a[i])) q[i]=a[i],s2.erase(a[i]);
		}
		else if(s2.count(a[i])){
			q[i]=a[i];
			s2.erase(a[i]);
		}
	}
	for(int i=1;i<=n;i++){
		if(p[i]) continue;
		auto it=s1.lower_bound(a[i]);
		if(it==s1.begin()){
			cout<<"No"<<endl;
			return;
		}
		it--;
		p[i]=*it;
		s1.erase(it);
	}
	for(int i=1;i<=n;i++){
		if(q[i]) continue;
		auto it=s2.lower_bound(a[i]);
		if(it==s2.begin()){
			cout<<"No"<<endl;
			return;
		}
		it--;
		q[i]=*it;
		s2.erase(it);
	}
	for(int i=1;i<=n;i++){
		if(max(p[i],q[i])!=a[i]){
			cout<<"No"<<endl;
			return;
		}
	}
	cout<<"Yes"<<endl;
	for(int i=1;i<=n;i++) cout<<p[i]<<' ';
	cout<<endl;
	for(int i=1;i<=n;i++) cout<<q[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;
}

24.D. Twist the Permutation

长度为n的排列,n在[2,2e3]
操作:n次操作,第i次操作将前i个数循环右移任意次数

问最后能否得到数组a,无解输出-1
输出位移总数最小的方案数

已知最终的结果,要求输出中间的过程->逆推

由于最终结果已知,第n个数是最后一次操作移动的,那么可以得到最后一次操作的最小循环右移次数,并推得最后一次操作前的序列,以此类推

trick:

1.已知最终的结果,要求输出中间的过程->逆推

2.用map预先记录每个数的位置,已知循环右移后的序列,可以快速得到循环右移的次数

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e3+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]]=i;
	for(int i=n;i>=1;i--){
		int res=mp[i]%i;//循环右移的次数
		ans[i-1]=res;
		//将1到i循环左移res回到上一状态,相当于循环右移i-res
		for(int j=1;j<=i;j++){
			mp[j]=(mp[j]+i-res)%i;
		}
	}
	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;
}

25.D. Twist the Permutation

n * m的表格 (n和m均小于等于100)
均为0和1
数据小,考虑暴力

进行着色,0表示白色,1表示黑色
一开始全是0,即全是白色
操作:选择子矩形,将其变成国际象棋的颜色(左上角为白色)
输出着色方案,无解输出-1
最左上角的格子必须是0,否则直接-1

从后往前操作,用1 * 2的矩阵去涂色

坑点:

想到从后往前用1 * 2的矩阵,但是没想到可以用竖着的1 * 2矩阵

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=110;
char s[N][N];
int n,m;
struct node{
	int x1,y1,x2,y2;
};
void solve() {
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>s[i][j];
		}
	}
	if(s[1][1]=='1'){
		cout<<-1<<endl;
		return;
	}
	vector<node>ans;
	for(int i=n;i>=1;i--){
		for(int j=m;j>=1;j--){
			if(s[i][j]=='1'&&j>=2) ans.push_back({i,j-1,i,j});
			else if(s[i][j]=='1'&&j==1) ans.push_back({i-1,j,i,j});
		}
	}
	cout<<ans.size()<<endl;
	for(auto v:ans) cout<<v.x1<<' '<<v.y1<<' '<<v.x2<<' '<<v.y2<<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.C. Divisor Chain

目标:将x减为1

可以减x的因子,但是同一个数最多只能减两次
最多需要减1000次,一定有解

由于任何一个数都都可以由若干个2的幂次构成
转化成2进制

先减到2的幂次,然后就好办了

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int x;
void solve() {
	cin>>x;
	string s;
	int a=x;
	int b=x;
	while(a){
		s=(char)((a&1)+'0')+s;
		a>>=1;
	}
	vector<int>ans;
	int len=s.size();
	//将x变为2的幂次
	for(int i=len-1;i>=1;i--){
		if(s[i]=='1') {
			ans.push_back(1ll<<(len-1-i));
			x-=(1ll<<(len-1-i));
		}
	}
	while(x!=1){
		ans.push_back(x/2);
		x/=2;
	}
	cout<<ans.size()+1<<endl;
	cout<<b<<' ';
	for(auto v:ans) cout<<b-v<<' ',b-=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;
}

27.C. Permutation Operations

长度为n的排列
操作:共n次操作,第i次操作选择一个非空后缀,将它们均加i(可以对同一后缀操作很多次)
使得逆序对数量最少

从大到小,整个后缀同时加应该是最优的,因为一下子就增加这么多的正序对
但是交上去错了,这样并不是最优的

发现我们完全可以让逆序对的数量为0

比如说5 3 2 4 1

我们可以让[2,5]都加5,即在第5次操作[2,5],在第3次操作[3,5],在第2次操作[4,5],在第4次操作[5,5]

比较巧妙,当作案例

trick:

两个相邻的数(均为正),要想让小的数大于大的数,让小数加上大的数

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[N];
int ans[N];
int n;
void solve() {
	cin>>n;
	memset(ans,0,sizeof ans);
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=2;i<=n;i++) ans[a[i-1]]=i;
	for(int i=1;i<=n;i++){
		if(ans[i]) cout<<ans[i]<<' ';
		else cout<<1<<' ';
	}
	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.C. Ehab and a Special Coloring Problem

构造一个序列,使得从下标2到n中,如果两个下标互质,那么两数不相等
使得序列的最大值最小,无解输出-1

相差为1的两个数互质
两个质数互质

所有质数都互质
所以全部偶数都放1,然后如果是质数的话,那么就依次放不同的数
如果是合数的话,那么就找它的因数,然后和它的因数放一样的答案

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int n;
int ans[N];
bool st[N];
int prime[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;
	get_prime(n);
	ans[2]=1;
	int idx=1;
	for(int i=3;i<=n;i++){
		if(!st[i]) ans[i]=++idx;
		else{
			for(int j=2;j<=i/j;j++){
				if(i%j==0){
					ans[i]=ans[j];
					break;
				}
			}
		}
	}
	for(int i=2;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;
}

29.B. Gardener and the Array

长度为n的数组c

问是否有两个不同的子序列,f(a)= f(b),f是将所有数或
只要索引不完全一样,子序列就不同

或:有1出1

如果某个数该位上就它为1,那么称这个数是必要的

如果所有数都是必要的,那么不可能有两个不同的子序列,f(a)= f(b),f是将所有数或

否则可以令a为整个序列,b为在a中剔除一个非必要的

trick:
1.如果想看某位是不是就一个数为1,可以用map桶计数,来记录有多少个数在该位上为1

2.位运算一般的技巧是将所有数都位运算

3.或是有1出1,只要数a某位上已经为1了,那么只要或上了a,那么该位一定为1,如果b该位也为1,其它位为0,那么或上b结果不变,就称b是非必要的

如果某位就数c为1的话,那么就称c是必要的

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
	cin>>n;
	vector<vector<int>>e(n+1);
	map<int,int>mp;
	for(int i=0;i<n;i++){
		int k;
		cin>>k;
		for(int j=0;j<k;j++){
			int x;
			cin>>x;
			e[i].push_back(x);
			mp[x]++;
		}
	}
	int cnt=0;
	for(int i=0;i<n;i++){
		for(int j=0;j<(int)e[i].size();j++){
			if(mp[e[i][j]]==1){
				cnt++;
				break;
			}
		}
	}
	if(cnt==n) cout<<"No"<<endl;
	else 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;
}

30.C. Ice and Fire

依次枚举,如果当前为0的话,然后看已经几个连续的0就需要连续靠0赢几次,同理,看几个连续的1就靠1赢几次

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int n;
int ans[N];
string s;
void solve() {
	cin>>n;
	cin>>s;
	s=' '+s;
	int cnt_0=0,cnt_1=0;
	for(int i=1;i<=n-1;i++){
		if(s[i]=='0'){//当前为0,看有几个连续的0
			cnt_0++;
			ans[i+1]=i+1-cnt_0;
			cnt_1=0;
		} 
		else {
			cnt_1++;
			ans[i+1]=i+1-cnt_1;
			cnt_0=0;
		}
	}
	for(int i=2;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;
}

31.A. Qingshan Loves Strings 2

长度为n的01串
数据比较小,考虑暴力

好字符串:所有对称的字符均不相等
操作:在任意位置插入01
无解输出-1,有解肯定在300次操作以内

暴力模拟

trick:

string的insert函数:s.insert(pos,tmp) 将tmp字符串插入在下标为pos的前面

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
string s;
void solve() {
	cin>>n;
	cin>>s;
	vector<int>ans;
	int cnt=0;//操作了几次
	//对称的下标相加为s.size()-1
	for(int i=0;i<(int)s.size();i++){
		if(s[i]==s[(int)s.size()-1-i]){
			if(i==0){
				string tmp="01";
				if(s[0]=='1') s=tmp+s,ans.push_back(0),i=0;
				else ans.push_back((int)s.size()),s+=tmp;
			}
			else{
				if(s[i]=='0'){
					int pos=(int)s.size()-1-i;//在pos后面插入01
					string tmp="01";
					s.insert(pos+1,tmp);
					ans.push_back(pos+1);
				}
				else{
					int pos=i;//在pos前面插入01
					string tmp="01";
					s.insert(pos,tmp);
					ans.push_back(pos);
					i-=2;
				}
			}
			cnt++;
			if(cnt>300) break;
		}
	}
	for(int i=0,j=(int)s.size()-1;i<=j;i++,j--){
		if(s[i]==s[j]){
			cout<<-1<<endl;
			return;
		}		
	}
	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;
}

32.B. New Year’s Eve

1到n共n个数
从中最多选取k个数,使得选取的所有数异或和最大

最大的数为n,假设n转化成二进制一共cnt位,异或是按位异或,不可能可以进位,所以最多也就cnt位,那么最大也就是cnt位全都为1

那么只要选两个数,一个数为n,另一个数只要把n为0的位补成1就行了

trick:

位运算是按位的,对于参与位运算的若干数,首先最大的那个数转化成二进制后最左边那位肯定是1(忽略前导0),假设共cnt位,按位运算是不可能进位的,所以这些数进行位运算最大也就是cnt位全部为1

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,k;
void solve() {
	cin>>n>>k;
	int ans=0;
	int num=1;
	if(k==1){
		cout<<n<<endl;
		return;
	}
	while(n){
		ans^=num;
		num<<=1;
		n>>=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;
}

33.C. Insert Zero and Invert Prefix

长度为n的01序列
操作:n次操作,第i次操作在0到i-1之间选择一个整数p,在序列b的p位置后插入0,并反转它前面所有元素

最后一个必须是0,否则无解

从后往前,数连续1的个数cnt,可以先产生cnt个0,然后将cnt个0反转为1

trick:

1.操作是插入一个0并将前缀反转,并不会影响后面的序列,考虑从后往前

2.01反转,考虑一段一段,连续cnt个1可以将连续cnt个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;
	for(int i=1;i<=n;i++) cin>>a[i];
	if(a[n]==1){
		cout<<"NO"<<endl;
		return;
	}
	cout<<"YES"<<endl;
	int cnt=0;
	for(int i=n-1;i>=0;i--){
		if(a[i]==1){
			cnt++;
			cout<<0<<' ';
		}
		else{
			cout<<cnt<<' ';
			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;
}

34.B. Binary String Constructing

构造长度为a+b的01串,刚好a个0,b个1
刚好有x对相邻对不相等
一定有解

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int a,b,x;
void solve() {
	cin>>a>>b>>x;
	if(a<=b){//1比较多
		if(x%2==1){
			for(int i=0;i<x/2;i++) cout<<"10";
			for(int i=0;i<b-x/2;i++) cout<<1;
			for(int i=0;i<a-x/2;i++) cout<<0;
			cout<<endl;
		}
		else{
			for(int i=0;i<x/2;i++) cout<<"10";
			for(int i=0;i<a-x/2;i++) cout<<0;
			for(int i=0;i<b-x/2;i++) cout<<1;
			cout<<endl;
		}
	}
	else{
		if(x%2==1){
			for(int i=0;i<x/2;i++) cout<<"01";
			for(int i=0;i<a-x/2;i++) cout<<0;
			for(int i=0;i<b-x/2;i++) cout<<1;
			cout<<endl;
		}
		else{
			for(int i=0;i<x/2;i++) cout<<"01";
			for(int i=0;i<b-x/2;i++) cout<<1;
			for(int i=0;i<a-x/2;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;
}

35.A. Packets

n个硬币

将n枚硬币分成若干组,使得1到n这些数都可以由某几组构成
求最少分成几组

要使组数最少,那么每组硬币数可以跨度大一些,想到2的幂次

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
	cin>>n;
	int ans=0;
	for(int i=0;;i++){
		int x=(1ll<<i);
		if(n>=x){
			ans++;
			n-=x;
		}
		else break;
	}
	if(n) ans++;
	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;
}

36.C. Insert and Equalize

长度为n的数组a(数[-1e9,1e9])每个数均不同
操作:
首先在末尾加入一个没出现过的数,选择一个正整数x
然后每次操作将x加到其中一个元素上

目标是让所有元素相等,问最少操作几次

先排个序
只能变大,不能变小,那么就都变成最大的那个

要使操作次数少,那么每次加的数要大一些,所有其它数和最大值的差值的gcd即为x,得到x之后就很简单了

那么有没有可能a[n+1]作为最大的数,然后其它n个数和a[n+1]的差值全部取gcd会更大呢?

不可能!!!

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

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;
int gcd(int a,int b){
	if(b==0) return a;
	return gcd(b,a%b);
}
void solve() {
	cin>>n;
	map<int,int>mp;
	for(int i=1;i<=n;i++) cin>>a[i],mp[a[i]]++;
	if(n==1){
		cout<<1<<endl;
		return;
	}
	sort(a+1,a+1+n);
	int x=0;
	for(int i=1;i<=n-1;i++){
		x=gcd(x,a[n]-a[i]);
	}
	int res=a[n];
	while(mp[res]){
		res-=x;
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		ans+=(a[n]-a[i])/x;
	}
	ans+=(a[n]-res)/x;
	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.B. Vasya and Isolated Vertices

由n个顶点,m条边组成的无向图
求孤立顶点的最少和最多数量

最多:x个顶点连成完全无向图,所用边数大于等于m,然后剩下节点成为孤立节点
最少:两两顶点一对,用一条边,n-m*2

trick:

图论一定要有特判,n为1和m为0

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,m;
int cal(int x){//x个顶点
	return x*(x-1)/2;//成为完全无向图需要的边数
}
void solve() {
	cin>>n>>m;
	if(m==0){
		cout<<n<<' '<<n<<endl;
		return;
	}
	int l=1,r=n;
	while(l<r){
		int mid=(l+r)/2;
		if(cal(mid)>=m) r=mid;
		else l=mid+1;
	}
	cout<<max(0ll,n-m*2)<<' '<<max(0ll,n-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;
}

38.A. Fill in the Matrix

n*m的矩阵
每一行要求是1到m的排列
vi是第i列mex值
beauty:v1到vm的mex
要求使mex最大

构造矩阵

0 1 2 3
1 2 3 0
2 3 0 1
先构造m-1行
再0 1 2 3重复
答案为m+1

0 1 2 3
1 2 3 0
不够m-1行,答案为n+1

0 1 2 3 4
1 2 3 4 0

特判m为1的情况

trick:

1.构造矩阵,一边遍历一边输出,很少存到数组再输出的

2.构造矩阵,一般要特判n为1,m为1

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

39.B. Bit Flipping

长度为n的01串

操作:选择一位不反转,其余位均反转
一共操作k次,使得字典序最大

01反转看奇数次,偶数次,如果反转奇数次,那么反转,否则不变
尽可能把前面的0 变成1
总反转次数为k,该位的反转次数即为k减去该位上的数字
从前往后遍历,如果是1的话,就需要保持不变,如果k为奇数,那么该位先放个1,使得该位操作次数为偶数 ,如果k为偶数,那么该位不用放数字
如果是0的话,就需要反转,如果k为奇数,那么该位不用放数字,如果k为偶数 ,那么该位放个1,最后如果k还有剩余,那么就全放在最后一位

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

40.A. Oh Those Palindromes

长度为n的字符串,由小写字母组成

回文计数:回文子串的个数
重新排列字符串,使得回文子串个数最多

aabbccddfgggghhh

16+2+1+1+1+1+6+3

aabb

abba

枚举了好几种情况,发现都是一样的排在一起是最多的

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
string s;
void solve() {
	cin>>n;
	cin>>s;
	multiset<char>s1;
	for(int i=0;i<n;i++) s1.insert(s[i]);
	for(auto v:s1) 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;
}

41.C. Salyg1n and the MEX Game

共n个不同的整数在集合S中
Alice和Bob博弈
Alice先行

Alice在集合S中添加一个S中没出现过的正整数x([0,1e9])
Bob在集合S中移除一个数y,y必须严格小于S中的最后一个数

当Bob移除不了数或者Alice走了n+1步,游戏结束,结果为mex(S)

Alice使结果最大化,Bob使结果最小化

Alice先行,先补上mex

然后当Bob移除一个数,Alice就把那个数加回来

trick:

1.交互题,程序中需要输入的就正常输入,需要输出的就在每个输出语句后面加上一句cout.flush();

2.博弈题,如果按照最优策略的话,根据对方来出招,比如说我方为增加,对方为移除,我方增加肯定是有利于我方的,对方移除肯定是有利于对方的,可以当对方移除时,我们就加回来是一个很好的策略

#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]]=1;
	int x;//没有出现的第一个数
	for(int i=0;i<n;i++){
		if(!mp[i]){
			x=-1;
			break;
		}
	}
	cout<<x<<endl;
	fflush(stdout);
	int y;
	while(cin>>y){
		if(y<0) break;
		cout<<y<<endl;
		fflush(stdout);
	}
}
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. Labs

nn个实验室建在不同的高度,1到nn,高度依次递增

高处的实验室可以向低处的实验室输送水,一次输送一单位的水

将n*n个实验室分成n组,每组包含n个实验室
f(A,B):A组向B组输水,可以输多少单位的水

构造一种分组,使得任意两组的f,所有情况取min,尽可能大

从样例寻求到了一种构造方法:
1到n分别放在每一组中
n+1到2*n分别放到每一组中
以此类推
放的方式是正序,逆序,正序,逆序…

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
	cin>>n;
	int flag=1;
	vector<vector<int>>e(n+1);
	int cnt=0;
	for(int i=1;i<=n;i++){//共n组
		if(flag){//正序
			for(int j=1;j<=n;j++){
				e[j].push_back(++cnt);
			}
		}
		else{
			for(int j=n;j>=1;j--){
				e[j].push_back(++cnt);
			}
		}
		flag^=1;
	}
	for(int i=1;i<=n;i++){
		for(auto v:e[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;
}

43.B. Painting Pebbles

一共n堆鹅卵石
ai为第i堆鹅卵石的数量
构造用k种颜色给每颗鹅卵石上色
使得任意两堆鹅卵石中颜色c的数量最多差1个,要求k种颜色都满足,无解输出NO

平均分配
一遍一遍遍历,只要每组都还有石头可涂,那么每组涂一个(用同一种颜色)
只要有任何一组没有石头可涂,那么这组涂完之后(用同一种颜色),就换颜色
直到所有的石头都被涂上色
如果颜色不够了,就无解

数据比较小,直接暴力

#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;
	int color=1;
	int sum=0;
	for(int i=1;i<=n;i++) cin>>a[i],sum+=a[i];
	vector<vector<int>>ans(n+1);
	int cnt=0;//涂了几个石头
	while(1){
		bool ok=true;
		for(int i=1;i<=n;i++){
			if((int)ans[i].size()<a[i]){//还有石头可涂
				ans[i].push_back(color);
				cnt++;
			}
			else ok=false;
		}
		if(!ok) color++;
		if(color>k||cnt==sum) break;
	}
	if(cnt<sum){
		cout<<"NO"<<endl;
		return;
	}
	cout<<"YES"<<endl;
	for(int i=1;i<=n;i++){
		for(auto v:ans[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;
}

44.B. Sonya and Exhibition

长度为n的01串
beauty:[l,r]中0的个数和1的个数的乘积
给定m个区间

构造01串,使得m个区间美之和最大

和一定,差越小,积越大
0和1平均分配

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,m;
void solve() {
	cin>>n>>m;
	while(m--){
		int l,r;
		cin>>l>>r;
	}
	int flag=1;
	for(int i=0;i<n;i++){
		if(flag) cout<<1;
		else cout<<0;
		flag^=1;
	}
	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.A. Difference Row

长度为n的数组a(数[-1000,1000])n大于等于2

排列,使得相邻两数差(前一个减后一个)之和最大,并且排列的字典序最小

最后的值为x1-xn,所以x1放最大,xn放最小,由于要字典序最小,中间升序

#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;
	for(int i=1;i<=n;i++) cin>>a[i];
	sort(a+1,a+1+n);
	reverse(a+1,a+1+n);
	vector<int>ans;
	for(int i=2;i<=n-1;i++) ans.push_back(a[i]);
	sort(ans.begin(),ans.end());
	cout<<a[1]<<' ';
	for(int i=0;i<(int)ans.size();i++) cout<<ans[i]<<' ';
	cout<<a[n]<<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.B. Neko Performs Cat Furrier Transform

对于正整数x(数[1,1e6])
完美长猫数:转化为二进制数全为1
操作:A.将x和一个完美长猫数异或 B.+1

第奇数次操作A,第偶数次操作B
最多执行40次,一定有解
将x变成完美长猫数

和1异或相当于01反转
从高位到低位一位一位反转

但是要注意,加1可能会进位,导致前面的1变成0,所以每次都从最高位开始找

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int x;
void solve() {
	cin>>x;
	int xx=x;
	int xxx=x;
	int cnt=-1;//x转化为二进制最高位是第几位
	while(xx){
		cnt++;
		xx>>=1;
	}
	vector<int>ans;
	while(x&(x+1)){
		for(int i=cnt;i>=0;i--){
			if((x&(1ll<<i))==0){//如果该位是0的话,就反转为1
				x^=((1ll<<(i+1))-1);
				if(x&(x+1)) x++;
				ans.push_back(i+1);
			}
		}
	}
	if(xxx%2==0) cout<<ans.size()*2-1<<endl;
	else cout<<ans.size()*2<<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;
}

47.C. Dividing the numbers

将1到n分到两个集合中,两个集合都不能为空,使得两组整数之和的差值最小

任意四个连续的数,x,x+1,x+2,x+3,可得x+x+3=x+1+x+2

所以每四个连续的数可以抵消,最后看n%4即可

trick:

任意四个连续的数,x,x+1,x+2,x+3,可得x+x+3=x+1+x+2

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
	cin >> n;
	int x = n % 4;
	if (x == 1 || x == 2) cout << 1 << endl;
	else cout << 0 << endl;
	vector<int>ans;
	for (int i = n; i >= 4; i -= 4) {
		ans.push_back(i);
		ans.push_back(i - 3);
	}
	if (x == 3) ans.push_back(3);
	else if (x == 2) ans.push_back(2);
	cout << ans.size() << ' ';
	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;
}

48.A. Lucky Permutation Triple

构造三个长度为n的排列,0到n-1各出现一次
满足任意i,(ai+bi)%n = ci
无解输出-1

当n为奇数时,可以构造a为0到n-1,b为0到n-1
猜测n为偶数时无解

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

49.B. Students in Railway Carriage

有n个连续的座位,.表示位置是空的,*表示有人

a名学生程序员,b名学生运动员,问最多有几人可以做到座位上,学生程序员不能连续,学生运动员不能连续

交替放置
优先放多的那个
取出一段一段连续的.

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,a,b;
string s;
void solve() {
	cin>>n>>a>>b;
	cin>>s;
	if(a<b) swap(a,b);
	vector<int>ans;//存放大于等于2的
	int sum=0;//统计等于1的个数
	int cnt=0;
	for(int i=0;i<n;i++){
		if(s[i]=='.') cnt++;
		else{
			if(cnt>=2) ans.push_back(cnt);
			else if(cnt==1) sum++;
			cnt=0;
		}
	}
	if(cnt>=2) ans.push_back(cnt);
	else if(cnt==1) sum++;
	int res=0;
	for(int i=0;i<(int)ans.size();i++){
		int flag=1;
		if(a<b) swap(a,b);//每次优先填大的
		for(int j=0;j<ans[i];j++){
			if(flag){
				if(a){
					res++;
					a--;
				}
			}
			else{
				if(b){
					res++;
					b--;
				}
			}
			flag^=1;
		}
	}
	//为1的填什么都行
	int res1=a+b;//剩下的
	res+=min(res1,sum);
	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;
}

50.B. Vika and Squares

一共有n个带颜色的罐子
ai表示颜色i罐子油漆的升数
从左到右给正方形涂颜料,一个正方形需要1升,必选按照x+1的循环顺序,起点颜料自定
问最多可以涂多少个正方形

取所有数的最小值minn,首先minn轮一定是可以的,然后就会出现很多个0造成阻断,那么只要统计连续非0个数最多是多少

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[2*N];
int n;
void solve() {
	cin>>n;
	int ans=0;
	int minn=2e9;
	for(int i=1;i<=n;i++) cin>>a[i],minn=min(minn,a[i]);
	for(int i=1;i<=n;i++) a[i]-=minn;
	for(int i=n+1;i<=2*n;i++) a[i]=a[i-n];
	ans+=minn*n;
	int cnt=0;
	int maxn=0;
	for(int i=1;i<=2*n;i++){
		if(a[i]) cnt++;
		else{
			maxn=max(maxn,cnt);
			cnt=0;
		}
	}
	if(cnt) maxn=max(maxn,cnt);
	cout<<ans+maxn<<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
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Codeforces是指在Codeforces比赛中预估自己在比赛结束后所能获得的数。这个数是考虑根据自己的表现和其他参赛者的表现来预估的。 Codeforces数是根据比赛中的排名和成功解决问题的数量来计算的。在每场比赛结束后,每位参赛者会根据其在比赛中的表现被配一个数。比赛中排名越高的参赛者获得的数也越高,而解决更多问题的参赛者同样也能获得更多数。 Codeforces有两种方法:一种是通过比赛中的实时排名来估计当前数,另一种是通过计算比赛中已解决问题的数来估计最终的总。 对于第一种方法,我们可以在比赛过程中观察自己在排名榜上的位置和其他参赛者的数。如果自己的排名越高,说明自己的数也会越高;如果其他人的数与自己相差较大,说明他们可能已经解决了更多的问题,因此可能获得更高的数。 对于第二种方法,我们可以根据已经解决的问题数量来估算总Codeforces的比赛系统会根据每个问题的难度和重要性配不同的数。因此,如果我们能成功解决更多的问题,我们也将获得更高的数。 总的来说,Codeforces是一个根据比赛中的排名和解决问题的数量来估计自己数的过程。但是,由于每场比赛的题目和参赛人数不同,预估数可能有一定的不确定性。因此,我们建议在比赛过程中持续观察排名榜和其他参赛者的情况,以及时作出调整和优化自己的策略。 ### 回答2: Codeforces是一个在线的编程竞赛平台,每个竞赛都有一定的难度,需要通过编写代码来解决各种算法数据结构的问题。Codeforces的估指的是根据你在竞赛中的表现得出的一个评。 在Codeforces竞赛中,你会根据你的解题情况和提交的答案是否正确来获得数。每个问题都有一定的值,解决该问题可以获得该值的数。如果你的答案是正确的,你将获得该问题的数;如果你的答案是错误的,你将不会获得数。 Codeforces的估算法是基于Elo算法改进的。Elo算法是一种用于评估竞技选手水平的算法。该算法会根据你的表现和对手的水平来决定你的数变化。如果你击败了一个数比你高的选手,你的数可能会上升得更多;如果你输给一个数比你低的选手,你的数可能会下降得更多。 Codeforces的估也考虑了竞赛的参与人数。如果你在一个参与人数多的竞赛中获得了好的成绩,你的数可能会得到进一步的提升。相反,如果你在一个参与人数少的竞赛中获得了好的成绩,你的数可能会得到更少的提升。 总的来说,Codeforces的估是根据你的表现、对手的水平和竞赛的参与人数来计算的。通过持续参与竞赛并取得好的成绩,你的数将会逐渐提升。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值