codeforces 1500分

文章目录


看一眼,然后不看屏幕,去回忆

1.C. Partitioning the Array

对于x,y,x=y(mod m)等价于(x-y)mod m=0,即如果m是|x-y|的因子,那么x,y在模m的意义下同余

所以对于某个k值(k是n的因子,一个一个枚举即可),如果存在m大于等于2,满足a1,a1+k,a1+2 * k…在模m的意义下同余,a2,a2+k,a2+2 * k…在模m的意义下同余…那么k就是一个合法解,答案就加1,也就是说每隔k个的在模m的意义下同余,所以直接从头遍历,看其和其后第k个即可,然后都是模m等于0,所以m需要是它们所有的公因数,故求一个最大公因数,如果大于等于2的话就可以

还一种情况,就是每隔k个数都相等,那么最大公因数就是0,也是可行的

trick:

1.对于x,y,x=y(mod m)等价于(x-y)mod m=0,即如果m是|x-y|的因子,那么x,y在模m的意义下同余

2.a1,a1+k,a1+2 * k…在模m的意义下同余,a2,a2+k,a2+2 * k…在模m的意义下同余…也就是说每隔k个的在模m的意义下同余,所以直接从头遍历,看其和其后第k个是否同余即可,由于都是模m为0,所以m是每差k个的两数之差的绝对值的公因数

3.gcd(a,b),令a等于0,那么结果就是b,所以求很多数的最大公因数时,可以初始化为0,这样比较好,注意gcd为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;
int gcd(int a,int b){
	if(b==0) return a;
	return gcd(b,a%b);
}
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	int ans=0;
	for(int k=1;k<=n;k++){//枚举k
		if(n%k==0){
			int tmp=0;
			for(int i=1;i+k<=n;i++){
				tmp=gcd(tmp,abs(a[i+k]-a[i]));
			}
			if(tmp>=2||tmp==0) 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;
}

2.F. Greetings

一共有n个人
ai代表第i个人的起始位置,bi代表第i个人的结束位置(所有a,b数组的数均不同)
ai小于bi

均匀速
不存在追的上,只有它的终点大于(不存在等于)另一个时,才会相遇
通过手玩发现,包含和被包含关系才会相遇一次

trick:

这就作为一个案例,当遇到统计区间包含的问题时,可以有以下几种方法

另外,有一些小知识点,在看题解的时候注意到的,set的erase时间复杂度为O(logn),vector的erase时间复杂度为O(n)

枚举左端点(从小到大),一边枚举,一边把枚举过的区间的右端点放到set里,看有几个右端点是大于当前右端点的(利用set的二分upperbound),就是有几个区间包含它,就打几次招呼,set中自带的二分函数时间复杂度为O(logn),这样总的时间复杂度是O(nlogn),照理说,这样是没问题的,但是有一个问题就是set自带的二分函数返回的是迭代器,然后想要获得位置的话需要distance(迭代器1,迭代器2),它底层实现是遍历,也就是O(n),这样时间复杂度就超了,试了一下直接用迭代器减s.begin()是会报错的,这个方法就不能用了

#include<bits/stdc++.h>
#define endl '\n'
//#define int long long
using namespace std;
const int N=2e5+10;
int l[N],r[N];
int n;
struct node{
	int l,r;
	bool operator<(const node &W)const{
		return l<W.l;
	}
}q[N];
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) cin>>q[i].l>>q[i].r;
	sort(q+1,q+1+n);
	set<int>s;
	int ans=0;
	s.insert(q[1].r);
	for(int i=2;i<=n;i++){
		auto t=s.upper_bound(q[i].r);
		int d=distance(s.begin(),t);
		ans+=s.size()-d;
		s.insert(q[i].r);
	}
	cout<<ans<<endl;
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

于是改用树状数组,但是有一个问题就是数的范围太大了,达到了1e9,要想用树状数组的话,这些数值本身作为一个位置,空间肯定爆了,所幸数的个数比较小,于是先离散化,这样的话空间就不会爆了

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int l[N],r[N];
int tr[N];
int n;
vector<int>alls;
int find(int x){
	int l=0,r=alls.size()-1;
	while(l<r){
		int mid=(l+r)/2;
		if(alls[mid]>=x) r=mid;
		else l=mid+1;
	}
	return l+1;
}
int lowbit(int x)
{
	return x & -x;
}
int sum(int x)//求x位置前的前缀和
{
	int res=0;
	while(x) res+=tr[x],x-=lowbit(x);
	return res;
}
void add(int x,int c)//在x的位置上加上常数c
{
	while(x<=n) tr[x]+=c,x+=lowbit(x);
}
struct node{
	int l,r;
	bool operator<(const node &W)const{
		return l<W.l;
	}
}q[N];
void solve() {
	cin>>n;
	memset(tr,0,sizeof tr);
	for(int i=1;i<=n;i++) cin>>q[i].l>>q[i].r;
	//离散化
	alls.clear();
	for(int i=1;i<=n;i++){
		alls.push_back(q[i].r);
	}
	sort(alls.begin(),alls.end());
	sort(q+1,q+1+n);
	int ans=0;
	int pos=find(q[1].r);
	add(pos,1);
	for(int i=2;i<=n;i++){
		pos=find(q[i].r);
		int d=sum(pos);
		ans+=i-1-d;
		add(pos,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;
}

还有其它的方法,就是我们只要将左端点从小到大排序,然后发现,只要右端点逆序就存在一个包含区间(就是说如果左端点小,但是右端点大,那么它就把一个区间包含住了),所以只要统计逆序对的数量即可,用归并排序

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int l[N],r[N];
int c[N];
int n;
int ans;
struct node{
	int l,r;
	bool operator<(const node &W)const{
		return l<W.l;
	}
}q[N];
void mergesort(int l,int r){
	if(l==r) return;
	int mid=(l+r)/2;
	mergesort(l,mid),mergesort(mid+1,r);
	int i=l,j=mid+1,k=0;
	while(i<=mid&&j<=r){
		if(q[i].r<=q[j].r) c[k++]=q[i++].r;
		else{
			c[k++]=q[j++].r;
			ans+=mid-i+1;
		}
	}
	while(i<=mid) c[k++]=q[i++].r;
	while(j<=r) c[k++]=q[j++].r;
	for(int i=l,k=0;i<=r;i++) q[i].r=c[k++];
}
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) cin>>q[i].l>>q[i].r;
	sort(q+1,q+1+n);
	ans=0;
	mergesort(1,n);
	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.B. Plus and Multiply

无限集合生成:1在集合中,如果x在集合中,那么x*a和x+b都在集合中
问n是否在这个集合中

集合元素可以用 a k 1 a^{k_1} ak1+ k 2 k_2 k2*b表示,然后枚举k1即可,指数级别,最多枚举30次

trick:

数学

1.关于数学运算,手玩+推数学式子

2.无限集合生成问题,问某个数是否在里面,集合会不断产生数,数量是及其庞大的,不可能将全部数构造出来,可以用一个式子表示集合中的元素

3.当出现幂次时,可以往暴力枚举去想,因为指数级别,枚举不了几次的

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,a,b;
void solve() {
	cin>>n>>a>>b;
	int res=1;
	for(int i=0;i<=30;i++){
		if(res>n) break;
		if((n-res)%b==0){
			cout<<"Yes"<<endl;
			return;
		}
		res*=a;
	}
	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.B. Codeforces Subsequences

要求至少包含k个codeforces,要求序列长度最短

ccoddddeeeeefffooooooooorrccceeessss按顺序排总的个数即为c的个数o的个数…*s的个数,按顺序,个数只要相乘,这样长度相对较短

由于和一定差小积大,积一定差小和小,所以平均分乘积最大

trick:

和一定差小积大,积一定差小和小,当和一定时,平均分乘积最大,平均分的另一技巧:假设0到9共10个位置,从0到9循环放置1(因为总和什么的都不是定值,只知道大概一个上限 )

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int k;
string s="codeforces";
map<int,int>mp;
bool check(){
	int res=1;
	for(int i=0;i<10;i++){
		res*=mp[i];
	}
    if(res>=k) return true;
	return false;
}
void solve() {
	cin>>k;
	int cnt=0;
	mp.clear();
	while(1){
		mp[cnt]++;
		cnt++;
		if(cnt==10) cnt=0;
		if(check()) break;
	}
	for(int i=0;i<10;i++){
		for(int j=0;j<mp[i];j++){
			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;
}

5.D. Binary String To Subsequences

长度为n的01字符串

将字符串分成最少数量的子序列(可以跳着,但相对顺序不变),使得每个子序列01交替

贪心 ,可以放就放,不能放就生成一个新的集合

那么如何快速知道能不能放呢?将可以放的都放在set里,然后如何set的个数不为0,那么表示可以放,每次取set的头就行了

trick:
假设前面已经产生了很多个集合了,然后如何快速确定当前遍历到的数是否可以放在前面的集合里,只要一边遍历一边把可以放的集合序号放在set里,如果set的元素个数不为0,说明可以放,然后取set的头就行

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N];
int n;
string s;
void solve() {
	cin>>n>>s;
	int cnt=0;//集合的序号
	set<int>a0,a1;//a0表示可以放0,a1表示可以放1
	for(int i=0;i<n;i++){
		if(s[i]=='1'){
			if(a1.size()==0){
				cnt++;//开一个集合
				a0.insert(cnt);//集合cnt可以放0
				a[i]=cnt;
			}
			else{
				int x=*a1.begin();
				a1.erase(a1.begin());
				a0.insert(x);
				a[i]=x;
			}
		}
		else{
			if(a0.size()==0){
				cnt++;
				a1.insert(cnt);
				a[i]=cnt;
			}
			else{
				int x=*a0.begin();
				a0.erase(a0.begin());
				a1.insert(x);
				a[i]=x;
			}
		}
	}
	cout<<cnt<<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;
}

6.C. Omkar and Baseball

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

对于一个全排列
操作:对一个连续子序列,特殊交换元素(使得没有一个数处于原来的位置)
问最少需要几次可以有序,不会超过1e18

题目说证明过了次数不会超过1e18,就感觉好离谱,基本可以判定是唬人的了,次数应该很小

手玩,造复杂极端的样例,发现最多两次就可以,对于那些中间隔着些位置确定的数,需要花两次把它们复原,对于那些位置不正确的数,花一次,然后它们的最大值即最少次数

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 l=-1,r=-1;
	for(int i=1;i<=n;i++){
		if(a[i]!=i){
			l=i;
			break;
		}
	}
	for(int i=n;i>=1;i--){
		if(a[i]!=i){
			r=i;
			break;
		}
	}
	if(l==-1||r==-1){
		cout<<0<<endl;
		return;
	}
	bool ok=false;
	for(int i=l;i<=r;i++){
		if(a[i]==i){
			ok=true;
			break;
		}
	}
	if(ok) cout<<2<<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;
}

7.C. Binary String Reconstruction

给定一个01字符串s,一个整数x
重新构造字符串w

如果si为1,要么wi-x为1,要么 wi+x为1
如果si为0,如果i-x或者i+x存在,它们必须为0
一开始题目读反了,然后输出答案,发现如果有解的话,那么和答案一样
所以想到先构造出w,再反过来验证
如何构造:如果si为0,那么就把wi-x和wi+x设为0,其它均设为1

trick:

关于如何构造,使得检验后不符合就一定是无解

把必须一定的先填上一定的值,其它的可能值不固定,但是使得不仅满足条件,而且要过满,完全满足,这样,如果检验都不合法的话,一定无解

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
string s;
int x;
char w[N];
void solve() {
	cin>>s>>x;
	int n=s.size();
	s=' '+s;
	for(int i=1;i<=n;i++) w[i]='1';
	for(int i=1;i<=n;i++){
		if(s[i]=='0'){
			if(i-x>=1) w[i-x]='0';
			if(i+x<=n) w[i+x]='0';
		}
	}
	//验证 
	for(int i=1;i<=n;i++){
		if((i-x>=1&&w[i-x]=='1')||(i+x<=n&&w[i+x]=='1')){
			if(s[i]!='1'){
				cout<<-1<<endl;
				return;
			}
		}
		else{
			if(s[i]!='0'){
				cout<<-1<<endl;
				return;
			}
		}
	}
	for(int i=1;i<=n;i++) cout<<w[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.F. Binary String Reconstruction

给定相邻字符中1的个数的次数
构造01字符串,一定有解

tric:

构造题想到奇偶,包括奇偶位以及变量是奇数还是偶数的分类

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

9.D. Non-zero Segments

长度为n的数组a(数[-1e9,1e9],非 0)

插入一些数(任何数,无穷也行 ),使得所有连续子段的和都不为0

我只要插入一个无穷,那么包含它的连续子段和一定不为0
只要统计有几个连续子段和为0,然后在该段区间内插入数破坏掉就行,在外面插入是改变不了该段的和的

如何看连续子段和是否为0,利用前缀和,一边遍历一边统计, 然后记录前面出现过的前缀和 ,如果当前缀和前面出现过了,那么以当前遍历到的数为右端点的一段连续区间和为0,此时,我们只要在当前遍历到的数之前插入一个无穷大,那么就隔断了,然后全部清空,从当前的数重现开始统计前缀和

trick:

如何一边遍历一边统计连续子段和为0的区间个数:动态记录前缀和,一边遍历一边统计, 然后动态记录前面出现过的前缀和 (利用map),如果当前缀和前面出现过了,那么以当前遍历到的数为右端点的一段连续区间和为0,注意一开始要mp[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;
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	int sum=0;//动态记录前缀和
	map<int,int>mp;//动态记录出现过的前缀和
	mp[0]=1;
	int ans=0;
	for(int i=1;i<=n;i++){
		sum+=a[i];
		if(mp[sum]){//出现了一个连续区间和为0
			ans++;
			sum=a[i];
			mp.clear();
			mp[0]=1;
		}
		mp[sum]=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;
}

10.D2. Sage’s Birthday (hard version)

一共n个冰球,ai为第i个冰球的价格

只买便宜的冰球(价格小于左右两边)

将所有冰球重新排序,使得可以购买的冰球数量最大

造波谷,使得波谷数量最多
波峰和波谷必须交替出现
先拍个序,然后双指针,最大,最小,次大,次小,交替,因为有相同的数,所以这样不一定最多

可以通过奇偶位构造,将一半小的依次放在2,4,6,…,将后面的依次放在1,3,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;
	for(int i=1;i<=n;i++) cin>>a[i];
	sort(a+1,a+1+n);
	int x=n/2;
	multiset<int>s1,s2;
	for(int i=1;i<=x;i++) s1.insert(a[i]);
	for(int i=x+1;i<=n;i++) s2.insert(a[i]);
	int idx=2;
	while(s1.size()){
		ans[idx]=*s1.begin();
		s1.erase(s1.begin());
		idx+=2;
	}
	idx=1;
	while(s2.size()){
		ans[idx]=*s2.begin();
		s2.erase(s2.begin());
		idx+=2;
	}
	int res=0;
	for(int i=2;i<=n-1;i++){
		if(ans[i]<ans[i-1]&&ans[i]<ans[i+1]) res++;
	}
	cout<<res<<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;
}

11.A. And Matching

给定一个n,n为2的次幂

然后0到n-1共n个数,将它们两两一组配对,每组两个数相与,使得每组加起来刚好等于k,无解输出-1

关于2的次幂的位运算(假设n为2的次幂):

1.x & (n-1-x)=0

2.x&(n-1)=x

故当n-1不等于k时,就让n-1和k与,变成k

其它均两两与变为0

当n-1等于k时,可以让(n-1)&(n-2)=n-2,(n-3)&1=1,0 & 2=0,全部加起来,凑成n-1,即k,然后其它凑成0

trick:

关于2的次幂的位运算(假设n为2的次幂):

1.x & (n-1-x)=0

2.x&(n-1)=x

分析:

n为2的次幂,即一个1后面都是0,n-1则去掉那个1,将后面的0全部变为1(也就是全1)

那么n-1-x就是将x 0,1翻转,所以x & (n-1-x)=0

由于n-1全部都是1,所以x保留原来的1,x原来为0的,进行与运算也还是0,所以x&(n-1)=x

另外,n-1为11111

n-2为11110,n-3为11101

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
int n,k;
void solve() {
	cin>>n>>k;
	vector<PII>ans;
	if(k!=n-1){
		if(k){
			ans.push_back({k,n-1});
			ans.push_back({0,n-k-1});
		}
		else ans.push_back({k,n-1});
		for(int i=1;i<n/2;i++){
			if(i==k||i==n-k-1) continue;
//			cout<<i<<' '<<n-1-i<<endl;
			ans.push_back({i,n-1-i});
		}
		for(auto v:ans) cout<<v.first<<' '<<v.second<<endl;
		return;
	}
	ans.push_back({n-1,n-2});
	ans.push_back({1,n-3});
	ans.push_back({0,2});
	for(int i=3;i<n/2;i++) ans.push_back({i,n-1-i});
	int res=0;
	map<int,int>mp;
	mp[n-1]++;
	mp[n-2]++;
	mp[1]++;
	mp[n-3]++;
	mp[0]++;
	mp[2]++;
	for(auto v:mp){
		if(v.second>1){
			cout<<-1<<endl;
			return;
		}
	}
	for(auto v:ans) res+=(v.first&v.second);
	if(res==k){
		for(auto v:ans) cout<<v.first<<' '<<v.second<<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;
}

12.C. Minimum Ties

共n个球队
每两个球队进行一场比赛,平局各得1分,赢的得3分,输的不得分

要求输出每场比赛结果,使得所有球队分数相同,平局最少
一定有解

每个球队打n-1场比赛
每个球队赢的场和输的场数都一样,然后每个球队就先赢x场,保证好之后,剩下的只能是平和输,确定好平,剩下的就是输了

如果n为奇数的话,那么就每个球队赢(n-1)/2次,输(n-1)/2次,如果n为偶数的话,只能全部平局,通过反证法得到的,不对,如果n为偶数的话,同奇数,加一个平局

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
int n;
void solve() {
	cin>>n;
	vector<vector<int>>e(n+1);
	if(n%2==0){
		int x=(n-1)/2;//每个球队赢x场
		for(int i=1;i<n;i++){
			int sum=0;//赢了几场
			for(int j=i+1;j<=n;j++){
				if(sum<x) e[i].push_back(1);
				else if(sum==x) e[i].push_back(0);
				else e[i].push_back(-1);
				sum++;
			}
		}
	}
	else{
		int x=(n-1)/2;//每个球队赢x场
		for(int i=1;i<n;i++){
			int sum=0;//赢了几场
			for(int j=i+1;j<=n;j++){
				if(sum<x) e[i].push_back(1);
				else e[i].push_back(-1);
				sum++;
			}
		}
	}
	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;
}

13.C. Beautiful Sets of Points

在左下角为顶点(0,0),右上角为(n,m)的矩形区域中选择若干个点,满足点为整数,但是任意两点距离不为整数
问最多选取几个点(不能选取原点)

最多n*m个点,10000
我们选取的原则是任何两个点不能在同一行,也不能在同一列
从(n,0)开始,一直往左上方,直到超出边界

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
int n,m;
void solve() {
	cin>>n>>m;
	vector<PII>ans;
	int x=n,y=0;
	while(x>=0&&y<=m){
		ans.push_back({x,y});
		x--;
		y++;
	}
	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;
}

14.Problem - C - Codeforces

无限大正方形被分成很多单元格
给单元格涂上灰色
beautiful:所有灰色单元格都要相连,每个灰色单元格相邻灰色单元格数量为偶数,刚好有n个灰色单元格它周围的单元格都被涂色

一种构造万能的构造方法就是斜着构造

trick:

矩阵构造形状不定,斜着构造是一种方向

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
int n;
void solve() {
	cin>>n;
	vector<PII>ans;
	for(int i=0;i<=n+1;i++) ans.push_back({i,i});
	for(int i=0;i<=n;i++){
		ans.push_back({i,i+1});
		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;
}

15.C. Rings

长度为n的01字符串
n个戒指,0表示金,1表示银

找两个不同的连续子序列,长度至少是n的一半,将它们分别转化为10进制,保证两个数非互质,一定有解

当序列全为1时,只要选择[1,n/2]和[2,n/2+1]即可

当存在一个0时,有这样两种情况:

记第一个0的位置为pos

1010101->[pos,n]和[pos+1,n]

1111101->[1,pos]和[1,pos-1]

trick:

1.先考虑特殊的简单的情况,比如说先考虑全0和全1再考虑0和1都有,再比如说先考虑全是正数和全是负数再考虑正数和负数都有

2.如果构造题题目说一个数是另一个数的倍数,由于是我们自己构造,所以考虑特殊的,比如1和2

再比如说两个数互质,我们可以考虑特殊的2和3

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

16.C. Ehab and Path-etic MEXs

给定一棵树,共n个节点,n-1条边

在n-1条边上填入0到n-2之间的整数,填入的所有数不同

mex(u,v):从节点u到节点v经过的所有边上的整数的mex
使得所有的mex的最大值最小

找到度数最多的那个点,分支分别放0,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 d[N];
bool flag[N];
int n;
vector<vector<int>>e(N);
vector<int>ans;
int res[N];
map<PII,int>mp;
int idx;
void dfs(int u,int fa){
	flag[u]=true;
	if(fa!=-1) mp[{u,fa}]=mp[{fa,u}]=ans[++idx];
	for(auto v:e[u]){
		if(v==fa||flag[v]) continue;
		dfs(v,u);
	}
}
void solve() {
	cin>>n;
	memset(d,0,sizeof d);
	memset(flag,false,sizeof flag);
	ans.clear();
	mp.clear();
	for(int i=1;i<=n;i++) e[i].clear();
	vector<PII>c;
	for(int i=0;i<n-1;i++){
		int u,v;
		cin>>u>>v;
		c.push_back({u,v});
		e[u].push_back(v);
		e[v].push_back(u);
		d[u]++;
		d[v]++;
	}
	if(n==2){
		cout<<0<<endl;
		return;
	}
	int maxn=0;
	int maxi;
	for(int i=1;i<=n;i++){
		if(maxn<d[i]){
			maxn=d[i];
			maxi=i;
		}
	}
	if(maxn==2){
		ans.push_back(0);
		for(int i=2;i<=n-2;i++) ans.push_back(i);
		ans.push_back(1);
		idx=-1;
		for(int i=1;i<=n;i++){
			if(d[i]==1){
				dfs(i,-1);
				break;
			}
		}
	}
	else{
		mp[{maxi,e[maxi][0]}]=mp[{e[maxi][0],maxi}]=0;
		mp[{maxi,e[maxi][1]}]=mp[{e[maxi][1],maxi}]=1;
		mp[{maxi,e[maxi][2]}]=mp[{e[maxi][2],maxi}]=2;
		for(int i=3;i<=n-2;i++) ans.push_back(i);
		idx=-1;
		flag[maxi]=true;
		flag[e[maxi][0]]=true;
		flag[e[maxi][1]]=true;
		flag[e[maxi][2]]=true;
		for(auto v:e[e[maxi][0]]) if(!flag[v]) dfs(v,e[maxi][0]);
		for(auto v:e[e[maxi][1]]) if(!flag[v]) dfs(v,e[maxi][1]);
		for(auto v:e[e[maxi][2]]) if(!flag[v]) dfs(v,e[maxi][2]);
		for(int i=3;i<(int)e[maxi].size();i++) if(!flag[e[maxi][i]]) dfs(e[maxi][i],maxi);
	}
	for(int i=0;i<n-1;i++){
		res[i]=mp[{c[i].first,c[i].second}];
	}
	for(int i=0;i<n-1;i++) cout<<res[i]<<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. Factorials and Powers of Two

给定一个n,问最少几个不同的有权数的和为k,无解输出-1

阶乘比2的幂次还要恐怖很多,15的阶乘就刚好已经超过1e12了

2的40次刚好超过1e12

通过贪心,从高到低选择,这样个数最少,如果最后不能刚好等于n,则无解 ,但这样是不对的,分解2的次幂可以这样做,因为一个数的二进制表示是唯一的,但是其它这样做不行

预先预处理阶乘

因为可选择的阶乘的个数比较少,所以直接二进制枚举选和不选(状压),然后选完阶乘之和(不能超过n),剩下的数选2的幂次,只需要转化成2进制,看2进制表示下有几个1即可
那有没有可能无解,即选取的2的幂次和阶乘重复了,不可能!因为一个数表示成2进制是唯一的,可以看作一开始的数转化成若干个2的次幂,然后将一些2的次幂合成阶乘

样例中没有无解的情况,那么基本上就没有无解的情况了

trick:

1.样例中没有无解的情况,那么基本上就没有无解的情况了

2.通过贪心,从高到低选择,这样个数最少,如果最后不能刚好等于n,则无解 ,但这样是不对的,分解2的次幂可以这样做,因为一个数的二进制表示是唯一的,但是其它这样做不行

3.如果可选择的个数很少的话(比如 2的幂次,阶乘),可利用二进制枚举选与不选

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int fac[100];
int n;
void solve() {
	cin >> n;
	int ans = 2e9;
	for (int i = 0; i < (1ll << 15); i++) { //二进制枚举
		int sum = 0;
		int cnt = 0;
		for (int j = 0; j < 15; j++) { //二进制表示共15位
			if ((i >> j) & 1) {
				sum += fac[j + 1];
				cnt++;
			}
		}
		int res = n - sum;
		if (res >= 0) {
			while (res) {
				if (res & 1) cnt++;
				res >>= 1;
			}
			ans = min(ans, cnt);
		}
	}
	cout << ans << endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
	fac[0] = 1;
	for (int i = 1; i <= 15; i++) fac[i] = fac[i - 1] * i;
    cin>>t;
	while (t--) {
		solve();
	}
	return 0;
}

18.C. Friends and Gifts

5
5 0 0 2 4
用样例来解释题目
5,2,4已经存放好,剩下的为1和3,将剩下的1和3分别填入为0的位置,要求下标不能等于数值

trick:

要求下标不能等于数值,先随便填入,再行调整,对于那些下标等于数值的,进行移位即可

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int f[N];
int n;
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) cin>>f[i];
	set<int>s;
	for(int i=1;i<=n;i++) s.insert(i);
	vector<int>pos;
	for(int i=1;i<=n;i++){
		if(f[i]) s.erase(f[i]);
		else pos.push_back(i);
	}
	map<int,int>mp;
	for(auto v:pos){//先随便放进去,然后如果下标和值一样,那么就移位一下
		f[v]=*s.begin();
		s.erase(s.begin());
	}
//	for(int i=1;i<=n;i++) cout<<f[i]<<' ';
//	cout<<endl;
//	cout<<'='<<endl;
	vector<int>c;
	for(int i=1;i<=n;i++){
		if(f[i]==i){
			c.push_back(i);
		}
	}
	if(c.size()==0){
		for(int i=1;i<=n;i++) cout<<f[i]<<' ';
		cout<<endl;
		return;
	}
	if(c.size()==1){
		for(auto v:pos){
			if(v!=c[0]){
				swap(f[v],f[c[0]]);
				for(int i=1;i<=n;i++) cout<<f[i]<<' ';
				cout<<endl;
				return;
			}
		}
	}
	int tmp=f[c[0]];
	for(int i=0;i<(int)c.size()-1;i++){
		f[c[i]]=f[c[i+1]];
	}
	f[c[(int)c.size()-1]]=tmp;
	for(int i=1;i<=n;i++) cout<<f[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;
}

19.E. Restoring the Permutation

长度为n的排列,1到n

qi是p1到pi的最大值,q数组已知,逆推得到字典序最小的p和字典序最大的p

q中第一次出现的,那么对应位置p就可以确定
字典序最小的,只要在空的位置依次从小到大填入
字典序最大的,需要二分找到未填的数字中小于后缀最大值的最大的那个

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N];
int b[N],c[N];
bool flag[N];
int n;
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	memset(flag,false,sizeof flag);
	map<int,int>mp;
	set<int>s1,s2;
	for(int i=1;i<=n+1;i++) s1.insert(i),s2.insert(i);
	for(int i=1;i<=n;i++){
		if(!mp[a[i]]){
			s1.erase(a[i]);
			s2.erase(a[i]);
			flag[i]=true;
		}
		mp[a[i]]=1;
	}
	for(int i=1;i<=n;i++){
		if(flag[i]) b[i]=a[i];
		else{
			b[i]=*s1.begin();
			s1.erase(s1.begin());
		}
	}
	for(int i=1;i<=n;i++){
		if(flag[i]) c[i]=a[i];
		else{
			auto it=s2.lower_bound(a[i]);
			it--;
			c[i]=*it;
			s2.erase(it);
		}
	}
	for(int i=1;i<=n;i++) cout<<b[i]<<' ';
	cout<<endl;
	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;
}

20.A. Tile Painting

O(根号n)枚举n的因子,如果有两个以上因子,如果不是幂次的话,那么就可以通过中间变量,使得全部同一种颜色
否则,答案为最小的因子

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
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;
	if(n==1){
		cout<<1<<endl;
		return;
	}
	if(check(n)){
		cout<<n<<endl;
		return;
	}
	set<int>s;
	for(int i=2;i<=n/i;i++){
		if(n%i==0){
			s.insert(i);
			s.insert(n/i);
		}
	}
	if(s.size()!=1){
		int x=*s.begin();
		int m=n;
		int cnt=0;
		while(m!=1){
			m/=x;
			if(!m) break;
			cnt++;
		}
		if(pow(x,cnt)==n){
			cout<<x<<endl;
		}
		else cout<<1<<endl;
	}
	else{
		cout<<*s.begin()<<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. No More Inversions

n个元素
数组a为1到k,k到2*k-n
假设有一个大小为k的序列p,令b[i]=p[a[i]]来建立b
目标找到排列p,使得b中的逆序对数量小于等于a逆序对数量,并且b字典序最大
1到k,a[i]一定等于i,所以b[i]=p[a[i]]=p[i],所以p和b是同一个数组
首先,要保证逆序对数量不增加,要使得字典序最大,需要把大的放前面,那么保持逆序对数量不变,也就是k到n全都不变,然后个数和k差几个,就在前面从1开始补几个

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,k;
void solve() {
	cin>>n>>k;
	int x=n-k+1;//k,k-1,k-2...,n一共x个
	int y=k-(x-1);//第n个数为y
	for(int i=1;i<y;i++) cout<<i<<' ';
	for(int i=k;i>=y;i--) cout<<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. Vladik and fractions

将2/n表示成1/x+1/y+1/z,无解输出-1

2/n=1/n+1/(n+1)+1/(n*(n+1))

trick:

通过变量推x,y,z,想必x,y,z和n有关系,n变化,那么x,y,z也会变化,料想x,y,z是带n的

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

23.E2. Unforgivable Curse (hard version)

长度为n的字符串s和t
问能否将字符串s变为t
操作:交换距离为k或者k+1的两个字符

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,k;
string s,t;
void solve() {
	cin>>n>>k;
	cin>>s>>t;
	map<char,int>mp1,mp2;
	if(n-1<k){//没有元素可交换
		for(int i=0;i<n;i++){
			if(s[i]!=t[i]){
				cout<<"No"<<endl;
				return;
			}
		}
		cout<<"Yes"<<endl;
		return;
	}
	if(n-1==k){//只有第一个元素和最后一个元素可以交换
		if(s[0]!=t[0]) swap(s[0],s[n-1]);
		for(int i=0;i<n;i++){
			if(s[i]!=t[i]){
				cout<<"No"<<endl;
				return;
			}
		}
		cout<<"Yes"<<endl;
		return;
	}
	//元素以及元个数必须相等
	for(int i=0;i<n;i++) mp1[s[i]]++,mp2[t[i]]++;
	for(auto v:mp1){
		if(mp1[v.first]!=mp2[v.first]){
			cout<<"No"<<endl;
			return;
		}
	}
	//1到l,r到n均可以交换
	int l=n-k;
	int r=1+k;
//	cout<<l<<' '<<r<<endl;
	if(l<r){
		for(int i=l+1;i<=r-1;i++){
			if(s[i-1]!=t[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;
}

24.C. Sum on Subarrays

构造长度为n的数组a(数[-1000,1000])
使得k个区间的和为正数,其余区间和为负数,一定有解

对于连续x个正数,和为正的区间个数为x*(x+1)/2
利用二分找到小于等于k的第一个x,首先要连续x个30,然后再补上k-x*(x+1)/2个30,记为res,利用最大的负数-1000在中间断开,这样有些数凑不出来
利用一个适当的负数在中间断,将前面好多30抵消掉,剩下res个30作为左端点,一直到该负数,和为正,剩下填最大的负数-1000

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,k;
int cal(int x){
	return x*(x+1)/2;
}
void solve() {
	cin>>n>>k;
	if(k==0){
		for(int i=0;i<n;i++) cout<<-1<<' ';
		cout<<endl;
		return;
	}
	int l=0,r=n;
	while(l<r){
		int mid=(l+r+1)/2;
		if(cal(mid)<=k) l=mid;
		else r=mid-1;
	}
	int res=k-cal(l);
	int x=l-res;
	vector<int>ans;
	for(int i=1;i<=l;i++) ans.push_back(30);
	if(res==0){
		while((int)ans.size()<n) ans.push_back(-1000);
		for(int i=0;i<(int)ans.size();i++) cout<<ans[i]<<' ';
		cout<<endl;
		return;
	}
	if((int)ans.size()<n) {
		if(x==0) ans.push_back(-1);
		else ans.push_back(-x*31);
	}
	while((int)ans.size()<n) ans.push_back(-1000);
	for(int i=0;i<(int)ans.size();i++) cout<<ans[i]<<' ';
	cout<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
	cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

25.B. Coloring

给n个单元格上色
共m种颜色,第i种颜色必须用ai次
k个连续的单元格,颜色均不同

每k个连续的颜色必须不同,也就是说对于一种颜色,不能在连续k个中重复出现
k个为1组,假设有cnt组,那么同一种颜色需要分布在这k组中,依次一组一组放就行了,平均分配,特判一下最后一组不为k个的情况

trick:

着色问题,本质上是分配问题,将某种颜色分配到哪些组中,平均分配,将某种颜色从第一组开始,往后一组一组放

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,m,k;
void solve() {
	cin>>n>>m>>k;
	vector<int>a(m+1);
	for(int i=1;i<=m;i++) cin>>a[i];
	int cnt=n/k;//一共cnt个完整的k
	int res=n%k;//最后余下几个单元格
	if(res==0){
		for(int i=1;i<=m;i++){
			if(a[i]>cnt){
				cout<<"No"<<endl;
				return;
			}
		}
		cout<<"Yes"<<endl;
		return;
	}
	int sum=0;
	for(int i=1;i<=m;i++){
		if(a[i]>cnt+1){
			cout<<"No"<<endl;
			return;
		}
		if(a[i]==cnt+1) sum++;
	}
	if(sum>res){
		cout<<"No"<<endl;
		return;
	}
	cout<<"Yes"<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

26.Problem - 441C - Codeforces

将k个管子恰好放在n*m的矩阵中,一定有解

trick:

矩阵走蛇形,每次取若干个,遍历矩阵不是很方便,可以先按照蛇形遍历一遍,将其放入vector里,变成线性,然后就很方便了

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int, int>PII;
int n, m, k;
void solve() {
	cin >> n >> m >> k;
	int flag = 1;
	vector<PII>ans;
	for (int i = 1; i <= n; i++) {
		if (flag) {
			for (int j = 1; j <= m; j++) {
				ans.push_back({i, j});
			}
		} else {
			for (int j = m; j >= 1; j--) {
				ans.push_back({i, j});
			}
		}
		flag ^= 1;
	}
	int idx = 0;
	int cnt = 0;
	if (cnt != k - 1) {
		for (int i = 0; i < (int)ans.size(); i += 2) {
			cout << 2 << ' ' << ans[i].first << ' ' << ans[i].second << ' ' << ans[i + 1].first << ' ' << ans[i + 1].second << endl;
			cnt++;
			if (cnt == k - 1) {
				idx = i + 2;
				break;
			}
		}
	}
	int sum = 0;
	for (int i = idx; i < (int)ans.size(); i++) sum++;
	cout << sum << ' ';
	for (int i = idx; i < (int)ans.size(); i++) cout << ans[i].first << ' ' << ans[i].second << ' ';
	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. Swap Letters

由小写字母a和b组成的两个长度均为n的字符串s和t
操作:将si和ti互换
使得s和t相等,问最少几次操作

所有字符个数必须是偶数,否则无解,输出-1
已经相等的不用管了,对于不相等的,分成两类,第一类是s中为a,t中为b,第二类是s中为b,t中为a,分别放在vector1和vector2中,先将各自偶数个进行配对,然后每类若都余下一个,则两者配对

trick:

对于1到n遍历,然后1,2配对,3,4配对,…,如果n为奇数,那就遍历到n-1,一直i+=2,别遍历到n

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

28.A. Color the Picture

n * m个单元格
共k种颜色,ai为第i种颜色可以着色的最大单元格数
beautiful:每个单元格周围至少三个颜色和它相同
问能否使得图画beautiful

根据样例,每种颜色需要至少完整的两列或两行
分别讨论放列和放行

Codeforces Round #810 (Div. 1) A. Color the Picture 解题报告-CSDN博客

#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,k;
void solve() {
	cin>>n>>m>>k;
	for(int i=1;i<=k;i++) cin>>a[i];
	int sum=0;
	bool ok=false;
	for(int i=1;i<=k;i++){
		if(a[i]/m>=2) sum+=a[i]/m;
		if(a[i]/m>=3) ok=true;
	}
	if(sum>=n){
		if(n%2==0) {
			cout<<"Yes"<<endl;
			return;
		}
		else{
			if(ok) {
				cout<<"Yes"<<endl;
				return;
			}
		}
	}
	sum=0;
	ok=false;
	for(int i=1;i<=k;i++){
		if(a[i]/n>=2) sum+=a[i]/n;
		if(a[i]/n>=3) ok=true;
	}
	if(sum>=m){
		if(m%2==0) {
			cout<<"Yes"<<endl;
			return;
		}
		else{
			if(ok) {
				cout<<"Yes"<<endl;
				return;
			}
		}
	}
	cout<<"No"<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

29.F. Smaller

操作:
1.在字符串s末尾加字符串x共k次
2.在字符串t末尾加字符串x共k次

问有没有可能在重新排列后s的字典序小于t
YES,NO

只要统计次数就行

t中最大的不是a,那么让s的a放最前面,t不是a放最前面

在双方都只有a的情况,s中a的个数小于t中的

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int q;
void solve() {
	cin>>q;
	map<char,int>mp1,mp2;
	mp1['a']=1;
	mp2['a']=1;
	char maxn='a';
	while(q--){
		int d,k;
		cin>>d>>k;
		string x;
		cin>>x;
		if(d==1){
			for(int i=0;i<(int)x.size();i++) mp1[x[i]]+=k;
			bool ok=false;
			if(maxn!='a') ok=true;//t中最大的不是a,那么让s的a放最前面,t不是a放最前面
			else if(mp1['a']<mp2['a']&&mp1.size()==1) ok=true;//在双方都只有a的情况,s中a的个数小于t中的
			if(ok) cout<<"YES"<<endl;
			else cout<<"NO"<<endl;
		}
		else{
			for(int i=0;i<(int)x.size();i++) mp2[x[i]]+=k,maxn=max(maxn,x[i]);
			bool ok=false;
			if(maxn!='a') ok=true;//t中最大的不是a,那么让s的a放最前面,t不是a放最前面
			else if(mp1['a']<mp2['a']&&mp1.size()==1) ok=true;//在双方都只有a的情况,s中a的个数小于t中的
			if(ok) 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;
}

30.C. Smilo and Monsters

一共有n种怪兽,第i种怪兽有ai只
计数器x初始为0
操作:
1.在1到n中选择一种怪兽,杀死一只,计数器+1
2.在1到n中选择一种怪兽,杀死x只,计数器清零

问最少几次操作杀死所有怪兽
按怪兽数量从高到低排序
从低的一个一个累计,然后当累计到最高的那个,就把最高的杀完,不要再累计了,因为再高也不能同时杀死好几种
双指针
l指向大的,r指向低的,如果a[l]>=a[r],那么a[l]-=a[r],ans+=a[r],如果a[l]<a[r],那么ans+=a[l]+1,a[r]-=a[l]
在这里乱减,这样就和原思路偏离了
就按照思路实现就行

#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];
	sort(a+1,a+1+n);
//	for(int i=1;i<=n;i++) cout<<a[i]<<' ';
//	cout<<endl;
	int l=1,r=n;
	int ans=0;
	int sum=0;
	while(l<=r){
//		cout<<l<<' '<<r<<endl;
//		cout<<a[l]<<' '<<a[r]<<endl;
		if(l==r){//特判
			ans+=min(a[l],(a[l]-sum)/2+((a[l]-sum)%2!=0)+1);
			break;
		}
		if(sum+a[l]<a[r]){
			sum+=a[l];
			ans+=a[l];
			l++;
		}
		else if(sum+a[l]==a[r]){
			ans+=a[l];
			ans++;
			sum=0;
			l++;
			r--;
		}
		else{
			ans+=a[r]-sum;
			a[l]-=(a[r]-sum);
			ans++;
			sum=0;
			r--;
		}
//		cout<<sum<<endl;
//		cout<<ans<<endl;
//		cout<<"---------------"<<endl;
	}
	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;
}

31.G. Even-Odd XOR

构造一个长度为n的数组a,使得索引为奇数的数全部异或起立等于下标为偶数的数全部异或起来
n大于等于3,n等于3可能是特殊情况
样例没有无解,大概没有无解的情况

x ^ x=0,所以全部数异或起来等于0,反过来,一个序列异或起来等于0,将序列分成两个集合,不管怎么分,两个集合内部异或起来得到的结果肯定相等

于是转化为构造一个异或和为0的序列,利用相同的可抵消,比如tmp为1到i的异或和,后面再添2个数,那么x,tmpx,由于要保证所有数均不同,所以最后3个数可以大很多,$2^{30}$,$2^{30}-1$,$2^{30}$( 2 30 − 1 2^{30}-1 2301)^tmp,这样不可能会有相同的数

trick:

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

2.(2k)^(2k+1)=1

3.构造一个异或和为0的序列,利用相同的可抵消,比如tmp为1到i的异或和,后面再添2个数,那么x,tmpx,由于要保证所有数均不同,所以最后3个数可以大很多,$2^{30}$,$2^{30}-1$,$2^{30}$( 2 30 − 1 2^{30}-1 2301)^tmp,这样不可能会有相同的数

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

32.C. Search in Parallel

先将所有颜色按次数从高到低排序
拐点法
不妨设机器人1搜索时间少
将前i个放在机器人1里,将后面的放在机器人2里,i作为拐点
如果两个机器人搜索时间一样,那么平均放
想错了,这就是分配到两个集合的问题,贪心,哪个优就放在哪个集合中

如果比较复杂的代码不用一股脑写完,写到关键的地方先测试一下,不然到最后写完了发现错了,也不知道前面哪一步错了

trick:

1.如果比较复杂的代码不用一股脑写完,写到关键的地方先测试一下,不然到最后写完了发现错了,也不知道前面哪一步错了

2.分配到两个集合问题:一个思路是贪心,排序后,遍历,就两个选择,要么放在第一个集合,要么放在第二个集合,哪个优就放到哪个集合中,通过反向思考感受这样贪心的合理性

3.当然贪心还有拐点法,两个贪心思路比较一下,哪个更合理,自行抉择

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
int n,s1,s2;
void solve() {
	cin>>n>>s1>>s2;
	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());
	vector<int>res1,res2;
	int cnt1=1,cnt2=1;
	for(int i=0;i<(int)ans.size();i++){
		if(cnt1*s1<=cnt2*s2){
			res1.push_back(ans[i].second);
			cnt1++;
		}
		else {
			res2.push_back(ans[i].second);
			cnt2++;
		}
	}
	cout<<res1.size()<<' ';
	for(int i=0;i<(int)res1.size();i++) cout<<res1[i]<<' ';
	cout<<endl;
	cout<<res2.size()<<' ';
	for(int i=0;i<(int)res2.size();i++) cout<<res2[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;
}

33.A. Mashmokh and Numbers

构造长度为n的数组a(数[1,1e9])所有数不同
使得gcd(a[1],a[2])+gcd(a[3],a[4])+…=k,无解输出-1

两两一组,共分为n/2组
第一个数为k-(n/2-1)和第二个数为它的两倍,然后后面两两互质,gcd均为1即可

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

34.A2. Make Nonzero Sum (hard version)

长度为n的数组a(数[-1,0,1])
将数组a分成若干个区间,所有区间交替和加起来为0,无解输出-1

两个1组合为0,如果1的个数为奇数,那么无解

CF1753A2 Make Nonzero Sum (hard version) - Alex_Wei 的博客 - 洛谷博客 (luogu.com.cn)

trick:

构造题,可以先构造一个小小的区间满足条件,然后只需一直按照此法即可

#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;
	int sum=0;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		if(a[i]!=0) sum++;
	}
	if(sum%2){
		cout<<-1<<endl;
		return;
	}
	vector<int>ans;
	for(int i=1;i<=n;i++){
		if(a[i]!=0) ans.push_back(i);
	}
	if(ans.size()==0){
		cout<<1<<endl;
		cout<<1<<' '<<n<<endl;
		return;
	}
	vector<PII>res;
	int len=ans.size();
	if(ans[0]!=1) res.push_back({1,ans[0]-1});
	if(a[ans[0]]+a[ans[1]]==0){
		res.push_back({ans[0],ans[1]-1});
		res.push_back({ans[1],ans[1]});
	}
	else{
		if(ans[0]+1==ans[1]){
			res.push_back({ans[0],ans[1]});
		}
		else{
			res.push_back({ans[0],ans[1]-2});
			res.push_back({ans[1]-1,ans[1]});
		}
	}
	for(int i=2;i<len;i+=2){
		if(a[ans[i]]+a[ans[i+1]]==0){
			if(ans[i]!=ans[i-1]+1) res.push_back({ans[i-1]+1,ans[i]-1});
			res.push_back({ans[i],ans[i+1]-1});
			res.push_back({ans[i+1],ans[i+1]});
		}
		else{
			if(ans[i]+1==ans[i+1]){
				if(ans[i]!=ans[i-1]+1) res.push_back({ans[i-1]+1,ans[i]-1});
				res.push_back({ans[i],ans[i+1]});
			}
			else{
				if(ans[i]!=ans[i-1]+1) res.push_back({ans[i-1]+1,ans[i]-1});
				res.push_back({ans[i],ans[i+1]-2});
				res.push_back({ans[i+1]-1,ans[i+1]});
			}
		}
	}
	cout<<res.size()<<endl;
	for(int i=0;i<(int)res.size();i++){
		if(i!=(int)res.size()-1){
			cout<<res[i].first<<' '<<res[i].second<<endl;
		}
		else cout<<res[i].first<<' '<<max(n,res[i].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;
}

35.C. Divan and bitwise operations

Eval (cnblogs.com)

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

作为位运算的一个案例

trick:

位运算对每个位都是独立的,考虑分别对每一个位求解

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int mod=1e9+7;
int n,m;
void solve() {
	cin>>n>>m;
	int tmp=0;
	while(m--){
		int l,r,x;
		cin>>l>>r>>x;
		tmp|=x;
	}
	int ans=1;
	for(int i=0;i<n-1;i++){
		ans=ans*2%mod;
	}
	cout<<ans*tmp%mod<<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.A1. Binary Table (Easy Version)

n * m的01矩阵
操作:对于一个2 * 2,选择三个单元格进行01反转
使得全部变成0,一定有解,最多操作3nm次

这个3比较特殊,猜想3次就可以将一个单元格进行反转
对于一个2*2,如果希望对其中的某个单元格进行反转,其余不变,那么保证该单元格反转3次,其余单元格均反转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,x3,y3;
};
void solve() {
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>s[i][j];
		}
	}
	vector<node>ans;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(s[i][j]=='1'){
				if(i==n&&j==m){
					ans.push_back({i,j,i-1,j-1,i,j-1});
					ans.push_back({i,j,i-1,j-1,i-1,j});
					ans.push_back({i,j,i,j-1,i-1,j});
				}
				else if(i==n){
					ans.push_back({i,j,i-1,j+1,i-1,j});
					ans.push_back({i,j,i-1,j+1,i,j+1});
					ans.push_back({i,j,i-1,j,i,j+1});
				}
				else if(j==m){
					ans.push_back({i,j,i+1,j-1,i,j-1});
					ans.push_back({i,j,i+1,j-1,i+1,j});
					ans.push_back({i,j,i,j-1,i+1,j});
				}
				else{
					ans.push_back({i,j,i+1,j+1,i,j+1});
					ans.push_back({i,j,i+1,j+1,i+1,j});
					ans.push_back({i,j,i,j+1,i+1,j});
				}
			}
		}
	}
	cout<<ans.size()<<endl;
	for(auto v:ans) cout<<v.x1<<' '<<v.y1<<' '<<v.x2<<' '<<v.y2<<' '<<v.x3<<' '<<v.y3<<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.D. Weights Assignment For Tree Edges

n个顶点
bi表示i的父亲,如果bi=i,那么i为根

给定1到n的排列,p1,p2,…pn
给树的边赋权值,使得根到p1距离最小,到p2距离第二小,以此类推
无解输出-1

直接给点p[i]到根节点的距离赋值,距离本来就是从小到大给的,赋成0到n-1,满足权重不同

只要有一个点距离小于它的父节点,那么无解

一开始没想过来,当作案例

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int fa[N];
int dist[N];
int p[N];
int n;
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) cin>>fa[i];
	for(int i=1;i<=n;i++){
		cin>>p[i];
		dist[p[i]]=i-1;//直接给点p[i]到根节点的距离赋值,距离本来就是从小到大给的,赋成0到n-1
	}
	for(int i=1;i<=n;i++){
		if(dist[i]<dist[fa[i]]){
			cout<<-1<<endl;
			return;
		}
	}
	for(int i=1;i<=n;i++) cout<<dist[i]-dist[fa[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;
}

38.C. XOR and OR

01串a和b
操作:两个相邻字符x和y,其中一个变为x^y,另一个变为x|y
问能否将a变成b

11可以变成10和01
10可以变成11
01可以变成11

长度不同,无解
串a全为0,串b有1,无解
串b全为0,串a有1,无解
其它均有解

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
string a,b;
void solve() {
	cin>>a>>b;
	int cnta0=0,cntb0=0;
	if(a.size()!=b.size()){
		cout<<"NO"<<endl;
		return;
	}
	for(int i=0;i<(int)a.size();i++){
		if(a[i]=='0') cnta0++;
	}
	for(int i=0;i<(int)b.size();i++){
		if(b[i]=='0') cntb0++;
	}
	if(cnta0==(int)a.size()&&cntb0!=(int)b.size()){
		cout<<"NO"<<endl;
		return;
	}
	if(cntb0==(int)b.size()&&cnta0!=(int)a.size()){
		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;
}

39.C. Meaningless Operations

位运算不会进位,最大也只能把a的0全变成1,那么最大公约数也不可能超过这个,那么最大即为这个
但是只能选择一个数,不一定能补

当a大的时候,可选的数就多了,可以补
当a小的时候,需要特判一下
另外,当a的二进制全为1时,不能选0,得特判,既然是全1,选一个b,那么a & b =b,a ^ b= a - b
求b和a-b的最大公因数,直接求a的最大因数(小于a)
如果是质数,直接为1

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int x;
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>>x;
	if((x&(x+1))==0){
		if(check(x)) cout<<1<<endl;
		else{
			for(int i=2;i<=x/i;i++){
				if(x%i==0){
					cout<<x/i<<endl;
					return;
				}
			}
		}
	}
	else{
		int cnt=0;
		int ans=0;
		while(x){
			ans+=(1ll<<cnt);
			x>>=1;
			cnt++;
		}
		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;
}

40.B. Yet Another Array Partitioning Task

长度为n的数组a
将数组a分成k个子数组,每个子数组至少有m个元素
使得k个子数组的beauty之和最大

一共分成k组,每组选其中最大的m个,最多加km个数,那我们把最大的km个数都选上肯定是最优的
预处理,先把k*m个最大的数的下标全部存起来,然后每m个分到一组

#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,m,k;
void solve() {
	cin>>n>>m>>k;
	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());
	vector<int>pos;
	int sum=0;
	for(int i=0;i<k*m;i++) pos.push_back(ans[i].second),sum+=ans[i].first;
	sort(pos.begin(),pos.end());
	vector<int>res;
	for(int i=m-1;i<(int)pos.size();i+=m){
		res.push_back(pos[i]);
	}
	cout<<sum<<endl;
	for(int i=0;i<(int)res.size()-1;i++){
		cout<<res[i]<<' ';
	}
	cout<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

41.D. Vus the Cossack and Numbers

给定n个实数,n个实数的和为0
构造一个序列b,使得bi等于ai向上取整或者向下取整,满足b序列总和为0

如果不是整数的话,那么就可以加1,也可以减1
先将所有数加起来,如果刚好为0的话,那么就不需要修改了,直接取小数点前的数就可以了
否则如果为正数,那么就需要将负实数变小
如果为负数,就需要把正实数变大

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
double a[N];
int b[N];
int n;
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	int sum=0;
	for(int i=1;i<=n;i++) sum+=(int)a[i],b[i]=(int)a[i];
	if(sum==0){
		for(int i=1;i<=n;i++) cout<<b[i]<<endl;
		return;
	}
	if(sum>0){//需要减去sum个1,靠负实数
		int cnt=0;
		for(int i=1;i<=n;i++){
			if(a[i]>0) continue;
			if(a[i]==(int)a[i]) continue;
			if(cnt<sum) b[i]-=1,cnt++;
		}
	}
	else{//需要加上sum个1,靠正实数
		int cnt=0;
		for(int i=1;i<=n;i++){
			if(a[i]<0) continue;
			if(a[i]==(int)a[i]) continue;
			if(cnt<-sum) b[i]+=1,cnt++;
		}
	}
	for(int i=1;i<=n;i++) cout<<b[i]<<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. Card Game

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

很好的题目,直接看整个题目

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int mod=998244353;
const int N=100;
int c[N][N];
int mem[N][2];
int n;
int dfs(int n,int win){
	int ans;
	if(mem[n][win]!=-1) return mem[n][win];
	if(win){
		ans=c[n-1][n/2-1];
		ans=(ans+dfs(n-2,0))%mod;
	}
	else{
		ans=(c[n][n/2]-dfs(n,1)-1+mod)%mod;
	}
	return mem[n][win]=ans;
}
void solve() {
	cin>>n;
	mem[2][0]=0;mem[2][1]=1;
	cout<<dfs(n,1)<<' ';
	cout<<dfs(n,0)<<' ';
	cout<<1<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
	memset(mem,-1,sizeof mem);
	c[0][0]=1;
	for(int i=1;i<=60;i++){
		c[i][0]=1;
		for(int j=1;j<=60;j++){
			c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
		}
	}
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

43.A. 24 Game

n小于等于3,无解
234=24
5开始往后,如果个数为奇数,无解
如果/2后为奇数,那么1+5-6变成0,其它相邻变成1,然后1和1抵消
如果/2后为偶数,那么123*4=24,5开始,相邻变成1,1和1抵消
这样错了,只要相邻的两个数,就可以造出1来
24一直乘1

如果是偶数的话,那么1 * 2 * 3 * 4 = 24,后面两两相邻得到1
如果是奇数的话,后面两两相邻得到1,还留一个5,那么得用1,2,3,4,5来凑24,可以用5-3+1来凑3就可以了

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
	cin>>n;
	if(n<=3){
		cout<<"NO"<<endl;
		return;
	}
	if(n==4){
		cout<<"YES"<<endl;
		cout<<1<<" * "<<2<<" = "<<2<<endl;
		cout<<3<<" * "<<4<<" = "<<12<<endl;
		cout<<2<<" * "<<12<<" = "<<24<<endl;
		return;
	}
	cout<<"YES"<<endl;
	if(n%2){
		cout<<5<<" - "<<3<<" = "<<2<<endl;
		cout<<2<<" + "<<1<<" = "<<3<<endl;
		cout<<2<<" * "<<3<<" = "<<6<<endl;
		cout<<6<<" * "<<4<<" = "<<24<<endl;
		vector<int>res;
		for(int i=7;i<=n;i+=2){
			cout<<i<<" - "<<i-1<<" = "<<1<<endl;
			res.push_back(1);
		}
		for(int i=0;i<(int)res.size();i++){
			cout<<24<<" * "<<1<<" = "<<24<<endl;
		}
	}
	else{
		cout<<1<<" * "<<2<<" = "<<2<<endl;
		cout<<3<<" * "<<4<<" = "<<12<<endl;
		cout<<2<<" * "<<12<<" = "<<24<<endl;
		vector<int>res;
		for(int i=6;i<=n;i+=2){
			cout<<i<<" - "<<i-1<<" = "<<1<<endl;
			res.push_back(1);
		}
		for(int i=0;i<(int)res.size();i++){
			cout<<24<<" * "<<1<<" = "<<24<<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.C. Removing Columns

给定一个n*m的小写字母矩阵
操作:删掉一列
使得从上到小每行组成的字符串按字典序从小到大排序
问最少几次操作

数据比较小,考虑暴力
字典序优先看第一位,再看第二位,再看第三位…
那就先看第一列,如果有逆序的,那么删掉这一列,但是如果上一列的上一行比下一行字典序大,那么逆序没关系,如果相等,逆序有关系

如果第一列有逆序,肯定要删掉
如果没有删掉,那么对于那些上一行和下一行相等的要在下一列比较,否则不用比较

#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;
void solve() {
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>s[i][j];
		}
	}
	int res=0;
	map<int,int>mp;
	for(int j=1;j<=m;j++){
		vector<int>ans;
		bool ok=false;//是否删掉这一列
		for(int i=2;i<=n;i++){
			if(mp[i]) continue;
			if(s[i][j]<s[i-1][j]){
				ok=true;//删掉这一列
				break;
			}
			else if(s[i][j]>s[i-1][j]){
				ans.push_back(i);//后面的列不用管第i行和第i-1行了
			}
		}
		if(!ok){//不删这一列
			for(auto v:ans) mp[v]=1;//先将不用管的行存储起来,后面再标记,否则中途发现这一列需要删除,但是已经标记了,那么不容易把标记删掉
		}
		else res++;
	}
	cout<<res<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

45.C. Nastya Is Transposing Matrices

n * m的矩阵A和B
操作:对于正方形子矩阵进行行列转置
操作次数不限
问A能否变成B

对于子矩阵,每次变换都是关于主对角线对称,发现一条副对角线上(平行于副对角线的全部平行线)的数可以随便移动
现在就是枚举负对角线上的数,关键在于如何枚举:先在草稿纸上写出坐标变化
(1,1)
(1,2)->(2,1)
(1,3)->(2,2)->(3,1)
(1,4)->(2,3)->(3,2)->(4,1)

(2,4)->(3,3)->(4,2)
(3,4)->(4,3)
(4,4)

trick:

1.枚举负对角线上的数,关键在于如何枚举:先在草稿纸上写出坐标变化,但是不要用正方形,用n和m不同的,因为用正方形的话,行和列可能会搞混

2.对于某些数,如果可以自由移动,问是否可以变成另一些数,那么分别将两者排序,然后从头到尾一个一个比较,必须全部一样才nen成功变成

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=510;
int a[N][N],b[N][N];
int n,m;
void solve() {
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>a[i][j];
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>b[i][j];
		}
	}
	vector<vector<int>>res1(n+m),res2(n+m);
	int cnt=0;
	for(int i=1;i<=m;i++){
		for(int j=1;j<=min(n,i);j++) res1[cnt].push_back(a[j][i+1-j]),res2[cnt].push_back(b[j][i+1-j]);
		cnt++;
	}
	for(int i=2;i<=n;i++){
		for(int j=m;i+m-j<=n&&j>=1;j--) res1[cnt].push_back(a[i+m-j][j]),res2[cnt].push_back(b[i+m-j][j]);
		cnt++;
	}
//	for(int i=0;i<cnt;i++){
//		for(int j=0;j<(int)res1[i].size();j++) cout<<res1[i][j]<<' ';
//		cout<<endl;
//		for(int j=0;j<(int)res2[i].size();j++) cout<<res2[i][j]<<' ';
//		cout<<endl;
//	}
	for(int i=0;i<cnt;i++){
		sort(res1[i].begin(),res1[i].end());
		sort(res2[i].begin(),res2[i].end());
//		for(int j=0;j<(int)res1[i].size();j++) cout<<res1[i][j]<<' ';
//		cout<<endl;
//		for(int j=0;j<(int)res2[i].size();j++) cout<<res2[i][j]<<' ';
//		cout<<endl;
		for(int j=0;j<(int)res1[i].size();j++){
			if(res1[i][j]!=res2[i][j]){
				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;
}

46.B. Pasha and Tea

一共有n个男生,n个女生
有2 * n个茶杯,第i个茶杯最多装ai毫升水
大茶壶最多装w毫升水

给每个女孩x毫升水,每个男孩2 * x毫升水
问最多用多少毫升水

装水量受小茶杯容量的限制和大茶壶容量的限制
女生用容量小的小茶杯,男生用容量大的小茶杯,受小容量限制,分别找到最小的容量和第n+1小的容量,然后w/n给这两个倒水
二分答案,找到最大的实数使得x小于等于最小的容量,2 * x小于等于第n+1小的容量,3 * x小于等于w/n
答案为3xn

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define eps 1e-11
using namespace std;
const int N=1e5+10;
double a[2*N];
int n;
double w;
void solve() {
	cin>>n>>w;
	for(int i=1;i<=2*n;i++) cin>>a[i];
	sort(a+1,a+1+2*n);
	double cup1=a[1],cup2=a[n+1];
	double w1=w/n;
	double l=0,r=w1;
	while(r-l>eps){
		double mid=(l+r)/2;
		if(mid<=cup1&&2*mid<=cup2&&3*mid<=w1) l=mid;
		else r=mid;
	}
	printf("%.8f\n",3*l*n);
}
signed main() {
//	ios::sync_with_stdio(false);
//	cin.tie(0);
//	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

47.Problem - 1043C - Codeforces

一个只包含小写字母a和b的字符串
从左到右按顺序枚举每个前缀,要么翻转要么不翻转,问怎么能使最终字典序最小

数据比较小,考虑暴力
字典序先看第一位,再看第二位,再看第三位…
第一位肯定放字典序最小的,然后翻转的话,最后一位翻到第一位,所以找到最小的字符,有多个记录下来
忘记只有a和b了,那就只要把a全部放在前面就行

记下连续a区间[l,r],翻转l-1和r

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
const int N=1010;
int ans[N];
string s;
void solve() {
	cin>>s;
	memset(ans,0,sizeof ans);
	vector<PII>res;
	int l,r;
	if(s[0]=='a') l=0;
	for(int i=0;i<(int)s.size()-1;i++){
		if(s[i]=='b'&&s[i+1]=='a') l=i+1;
		if(s[i]=='a'&&s[i+1]=='b'){
			r=i;
			res.push_back({l,r});
		}
	}
	if(s[(int)s.size()-1]=='a') r=(int)s.size()-1,res.push_back({l,r});
	for(int i=0;i<(int)res.size();i++){
		int l=res[i].first,r=res[i].second;
		if(l>0) ans[l-1]=1;
		ans[r]=1;
	}
	for(int i=0;i<(int)s.size();i++) cout<<ans[i]<<' ';
	cout<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

48.D. Prime Graph

构造一个n个顶点的无向图
满足边数为质数,每个顶点的度数为质数
无解输出-1

样例中没有无解的情况,猜测没有无解的情况

每个点的度数至少为2,那么把所有点连成一个环即可,此时边数为n,然后找一个大于等于n的最小的质数p,通过点x和点x+n/2来增加一条边,点x和点x+n/2的度数变为3仍为质数,直到增加到p,质数的密度是很大的,增加不了几条边的

trick:

1.将所有点都连成一个环,那么每个点的度数均为2

2.质数的密度很大,每几个点中就有一个质数

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

49.Problem - 1209C - Codeforces

给n个数字用两种颜色着色
将涂颜色1的数字放在前面,将涂颜色2的数字放在后面,要求序列非降序,无解输出-

最终序列就是升序序列
按照升序序列,先从头到尾把应该放在前面的挑选出来,直到没得挑选,涂为颜色1,其它都涂为颜色2

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int ans[N];
string s;
int n;
void solve() {
	cin>>n;
	cin>>s;
	for(int i=0;i<n;i++) ans[i]=2;
	string tmp=s;
	sort(tmp.begin(),tmp.end());
	int cnt=0;
	for(int i=0;i<n;i++){
		if(s[i]==tmp[cnt]){
			ans[i]=1;
			cnt++;
		}
	}
	vector<int>res;
	for(int i=0;i<n;i++){
		if(ans[i]==1) res.push_back(s[i]-'0');
	}
	for(int i=0;i<n;i++){
		if(ans[i]==2) res.push_back(s[i]-'0');
	}
	for(int i=1;i<(int)res.size();i++){
		if(res[i]<res[i-1]){
			cout<<'-'<<endl;
			return;
		}
	}
	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;
}

50.D. Knowledge Cards

首先要将n移到(n,m),然后是n-1,n-2,…
如果还没有取到n,那么其他牌都得先放在一个位置上等待,然后会造成堵塞,因为每个单元格占一张牌,所以我们要留一条从(1,1)到(n,m)的通路,那么最多堵(n-1)*(m-1),如果超过了就没有通路了,这边想错了,其实除起点和终点外只要留一个空位就可以从(1,1)送到(n,m),类似于数字华容道,数字华容道都是留一个位置,然后全部都可以随便移动

trick:

1.华容道只要留一个空位,然后全部都可以随便移动

2.当造矩阵样例时,不要选用m和n相等,容易将行和列搞混

#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, k;
void solve() {
	cin >> n >> m >> k;
	for (int i = 1; i <= k; i++) cin >> a[i];
	int wait = k; //待用卡牌
	int empty=n*m-2;//空位
	map<int,int>mp;//卡牌是否取出
	for (int i = 1; i <= k; i++) {
		mp[a[i]]=1;
		empty--;
		while(mp[wait]&&empty) wait--,empty++;
	}
	if(wait){
		cout<<"TIDAK"<<endl;
		return;
	}
	cout << "YA" << 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
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值