codeforces 1600分

文章目录

  • 1.[G. Special Permutation](https://codeforces.com/problemset/problem/1352/G)
  • 2.[D. Constructing the Array](https://codeforces.com/problemset/problem/1353/D)
  • 3.[C2. k-LCM (hard version)](https://codeforces.com/problemset/problem/1497/C2)
  • 4.[C. Circle of Monsters](https://codeforces.com/problemset/problem/1334/C)
  • 5.[A. Multiples of Length](https://codeforces.com/problemset/problem/1396/A)
  • 6.[C. Phoenix and Distribution](https://codeforces.com/problemset/problem/1348/C)
  • 7.[C. Game with Chips](https://codeforces.com/problemset/problem/1327/C)
  • 8.[B. Moderate Modular Mode](https://codeforces.com/contest/1603/problem/B)
  • 9.[A. Balance the Bits](https://codeforces.com/problemset/problem/1503/A)
  • 10.[C. Ehab and Prefix MEXs](https://codeforces.com/problemset/problem/1364/C)
  • 11.[A. Doremy's IQ](https://codeforces.com/problemset/problem/1707/A)
  • 12.[D. Grid-00100](https://codeforces.com/contest/1371/problem/D)
  • 13.[D. Say No to Palindromes](https://codeforces.com/problemset/problem/1555/D)
  • 14.[C. Chocolate Bunny](https://codeforces.com/problemset/problem/1407/C)
  • 15.[D. Min Cost String](https://codeforces.com/problemset/problem/1511/D)
  • 16.[B. Dima and a Bad XOR](https://codeforces.com/problemset/problem/1151/B)
  • 17.[D. Vupsen, Pupsen and 0](https://codeforces.com/problemset/problem/1582/D)
  • 18.[C. Remove Adjacent](https://codeforces.com/problemset/problem/1321/C)
  • 19.[C. Basic Diplomacy](https://codeforces.com/problemset/problem/1482/C)
  • 20.[A. Vitaly and Strings](https://codeforces.com/problemset/problem/518/A)
  • 21.[D. Secret Santa](https://codeforces.com/problemset/problem/1530/D)
  • 22.[C. Replacement](https://codeforces.com/problemset/problem/570/C)
  • 23.[D. Shocking Arrangement](https://codeforces.com/problemset/problem/1798/D)
  • 24.[D. Fixed Point Guessing](https://codeforces.com/problemset/problem/1698/D)
  • 25.[A. Sum in the tree](https://codeforces.com/problemset/problem/1098/A)
  • 26.[A. The Very Beautiful Blanket](https://codeforces.com/problemset/problem/1801/A)
  • 27.[D. Walking Between Houses](https://codeforces.com/problemset/problem/1015/D)
  • 28.[C. Tennis Championship](https://codeforces.com/problemset/problem/735/C)
  • 29.[D. Same Count One](https://codeforces.com/problemset/problem/1774/D)
  • 30.[D. Insert a Progression](https://codeforces.com/problemset/problem/1671/D)
  • 31.[C. Li Hua and Chess](https://codeforces.com/problemset/problem/1797/C)
  • 32.[C. Heap Operations](https://codeforces.com/problemset/problem/681/C)
  • 33.[C. Sequence Master](https://codeforces.com/problemset/problem/1806/C)
  • 34.[A. Sorting Railway Cars](https://codeforces.com/problemset/problem/605/A)
  • 35.[C. Sequence Transformation](https://codeforces.com/problemset/problem/1059/C)
  • 36.[E. Matrix and Shifts](https://codeforces.com/problemset/problem/1660/E)
  • 37.[D. Umka and a Long Flight](https://codeforces.com/problemset/problem/1811/D)
  • 38.[C. Equal Frequencies](https://codeforces.com/problemset/problem/1781/C)
  • 39.[C. Slava and tanks](https://codeforces.com/problemset/problem/877/C)
  • 40.[A. Plate Game](https://codeforces.com/problemset/problem/197/A)
  • 41.[A. Cows and Sequence](https://codeforces.com/contest/283/problem/A)
  • 42.[C. League of Leesins](https://codeforces.com/problemset/problem/1255/C)
  • 43.[B. Bear and Forgotten Tree 3](https://codeforces.com/problemset/problem/639/B)
  • 44.[B. Code For 1](https://codeforces.com/problemset/problem/768/B)
  • 45.[C. Anya and Smartphone](https://codeforces.com/problemset/problem/518/C)
  • 46.[C. The Phone Number](https://codeforces.com/problemset/problem/1017/C)
  • 47.[C. Plasticine zebra](https://codeforces.com/problemset/problem/1025/C)
  • 48.[C. New Year Book Reading](https://codeforces.com/problemset/problem/500/C)
  • 49.[D. Decorate Apple Tree](https://codeforces.com/problemset/problem/1056/D)
  • 50.[F. Alex's whims](https://codeforces.com/problemset/problem/1899/F)

1.G. Special Permutation

构造相邻元素差的绝对值在[2,4]的排列(1到n各出现一次)
无解输出-1

n小于4无解

n等于4时,可以3 1 4 2

当n大于4时,可以依次将5到n一个放最前面一个放最后面

trick:

差的绝对值在[2,4]均可以,可以考虑最特殊的2,只要依次前面放一个后面放一个就行

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

2.D. Constructing the Array

初始有n个0
共n次操作
第i次操作:选择连续为0的一段长度最长的[l,r](如果有好几个,选最左边的),让[l,r]最中间的那个值变为i
问最终序列是什么,一定有解,且唯一

trick:

1.优先可以考虑优先队列,可以按照题目中的优先自定义优先队列

2.自定义优先队列:

//结构体
struct node{
	int l,r,len;
	bool operator<(const node &W)const{
		if(len==W.len) return l>W.l;//反着来,优先l小的话,那么用大于号
		return len<W.len;//反着来,优先len大的话,那么用小于号
	}
};
priority_queue<node>q;
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N];
int n;
struct node{
	int l,r,len;
	bool operator<(const node &W)const{
		if(len==W.len) return l>W.l;
		return len<W.len;
	}
};
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) a[i]=0;
	priority_queue<node>q;
	q.push({1,n,n});
	for(int i=1;i<=n;i++){
		auto t=q.top();
		q.pop();
		int pos;
		if(t.len%2) pos=(t.l+t.r)/2;
		else pos=(t.l+t.r-1)/2;
		a[pos]=i;
		q.push({t.l,pos-1,pos-1-t.l+1});
		q.push({pos+1,t.r,t.r-pos});
	}
	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;
}

3.C2. k-LCM (hard version)

将正整数n分解为k个正整数,k个正整数的最小公倍数小于等于n/2,一定有解

先考虑特殊的,k为3的情况,因为题目中明确表示k大于等于3,那么k为3一定是特殊的,所以先考虑k为3,发现k为3只要三个数就行了,那么我们可以再补k-3个1就可以凑出k个数了,所以先让n减k-3,分解为3个数,然后再加上k-3个1即可

trick:

1.对于k大于等于3这一条件,我们可以认为k等于3是特殊的,先考虑这一情况

2.构造若干个数,要求它们的最小公倍数满足某个要求,其中1是最特殊的,我们可以先构造好几个数,满足要求,然后添加1就行了,想添几个1就添几个1

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,k;
void solve() {
	cin>>n>>k;
	n-=k-3;
	//先考虑k为3的情况
	if(n%2==0){//如果n为偶数
		if(n/2%2==0) cout<<n/2<<' '<<n/4<<' '<<n/4<<' ';//n/2是偶数
		else if((n/2-1)%2==0) cout<<n/2-1<<' '<<n/2-1<<' '<<2<<' ';//n/2是奇数
	}
	else cout<<n/2<<' '<<n/2<<' '<<1<<' ';//n是奇数
	for(int i=0;i<k-3;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;
}

4.C. Circle of Monsters

共n只怪兽围成一个圈,ai为第i只怪兽的生命值,我们每次发射一颗子弹,使其中一只怪兽扣除一个生命,如果一个怪兽死亡,会发生爆炸,对下一只怪兽造成bi伤害,可以连锁反应
问最少发射几颗子弹,杀死所有怪兽

前一个造成的爆炸伤害只能对后一个,要想这个怪兽被杀死,要么直接杀死,要么通过上一个怪兽的爆炸伤害杀死,方向是单向的,所以只要起点确定了,整个链的最小次数通过依次顺序来就行,于是需要考虑以每个点为起点,长度为n的链需要发射子弹数量,用前缀和

trick:

1.方向是单向的,考虑前缀和,如果成环了也一样,只不过长度乘个2而已

2.memset造成的惨案,超时,以后如果初始化的话用for循环

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

5.A. Multiples of Length

长度为n的数组a(数[-1e9,1e9])
操作:选择一段,可以向其中的数加上该段长度的倍数,每个数可以加的不同
操作刚好三次,使得所有元素为0
一定有解

第一次操作:1,1

第二次操作:1,n加上(-n)* a i a_i ai

第三次操作:2,n加上- a i a_i ai

trick:

操作某个数时,可以用上它本身的数值,乘,加,减混合使用

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

将字符串s的所有字符分配到k个集合中,分配完后集合不能为空
使得k个集合字典序最大的那个最小
顺序可以随便排

肯定要平均分
先将字符串s从小到大排个序,然后按从小到大顺序平均分到k个集合中
这样错了,比如aabbc分配到两个集合中,平均分配的话,最大的是abc,但是答案为abbc

首先,由于集合不能为空,所以都至少有一个字符,对字符串s从小到大排序,如果前k个字符不全一样,那么第k个集合就放s[k],然后就不放了,答案为s[k],如果前k个字符全一样,后面的字符也全部相等,那么就平均分配,如果后面的字符有不一样的,那么后面的字符全部放在第一个集合中

trick:

看到字典序,优先考虑第一位,再考虑第二位,一位一位看,因为前一位已经比较出来了,后面不管怎么样都没用

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

7.C. Game with Chips

样例中没有-1,大概是没有无解的情况

可以先将所有芯片移到角落,然后,再走遍所有的格子

注意要特判n为1以及m为1

trick:

矩阵要特判n为1以及m为1

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,m,k;
void solve() {
	cin>>n>>m>>k;
	if(n==1&&m==1){
		cout<<0<<endl;
		return;
	}
	if(n==1){
		cout<<2*m<<endl;
		for(int i=0;i<m;i++) cout<<'L';
		for(int i=0;i<m;i++) cout<<'R';
		return;
	}
	if(m==1){
		cout<<2*n<<endl;
		for(int i=0;i<n;i++) cout<<'U';
		for(int i=0;i<n;i++) cout<<'D';
		return;
	}
	for(int i=0;i<k;i++){
		int x,y;
		cin>>x>>y;
	}
	for(int i=0;i<k;i++){
		int x,y;
		cin>>x>>y;
	}
	cout<<n-1+m-1+n*m+n-1<<endl;
	for(int i=0;i<m-1;i++) cout<<'L';
	for(int i=0;i<n-1;i++) cout<<'U';
	int flag=1;
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			if(flag) cout<<'R';
			else cout<<'L';
		}
		if(i<n-1) cout<<'D';
		flag^=1;
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

8.B. Moderate Modular Mode

两个偶数x,y
找出一个整数n,满足n % x = y % n,一定有解

n=k1x+b
y=k2
n+b

n-y=k1x-k2n
(k2+1)n=k1x+y
k1,k2均为整数,所以k3n=(k1x+y)
所以n是k1*x+y的因数

当x=y时,取n为x

当x>y时,取n为x+y

当x<y时,根据%前后大小关系分类得知,n肯定在(x,y),,取n为y-y%x/2

trick:

1.遇到x % y = a % b,转化为 x=k1 * y + c,a=k2 * b + c

2.遇到x % y,要注意前后的大小关系,讨论x<y,x=y,x>y

3.遇到%,数形结合,画数轴,用距离来理解

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

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

9.A. Balance the Bits

长度为n的01串,n为偶数
构造两个平衡序列a和b,使得si为1时ai=bi,否则ai!=bi
无解输出No

首先先看s[i]为1的,肯定是对称的,另外s[i]为0的,也肯定是对称的

对于 s[i]=1 的, 如果这个 1 是在所有 1中的前半部分, 那就用左括号, 否则就用右括号。

对于 s[i]=0 的, 则如果是第奇数个0 的情况, 就用左括号, 否则就右括号。

比较难总结,作为构造括号序列的一个案例

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
string s;
void solve() {
	cin>>n;
	cin>>s;
	map<char,int>mp;
	for(int i=0;i<n;i++) mp[s[i]]++;
	int cnt_0=0,cnt_1=0;
	string a,b;
	for(int i=0;i<n;i++){
		if(s[i]=='1'){
			cnt_1++;
			if(cnt_1<=mp['1']/2) a+='(';
			else a+=')';
		} 
		else{
			cnt_0++;
			if(cnt_0%2) a+='(';
			else a+=')';
		}
	}
	for(int i=0;i<n;i++){
		if(s[i]=='1') b+=a[i];
		else{
			if(a[i]=='(') b+=')';
			else b+='(';
		}
	}
	//验证
	int sum=0;
	bool ok1=true;
	for(int i=0;i<n;i++){
		if(a[i]=='(') sum++;
		else sum--;
		if(sum<0) ok1=false;
	}
	if(sum) ok1=false;
	sum=0;
	bool ok2=true;
	for(int i=0;i<n;i++){
		if(b[i]=='(') sum++;
		else sum--;
		if(sum<0) ok2=false;
	}
	if(sum) ok2=false;
//	cout<<sum<<endl;
	if(!ok1||!ok2) cout<<"No"<<endl;
	else {
		cout<<"Yes"<<endl;
		cout<<a<<endl;
		cout<<b<<endl;
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

10.C. Ehab and Prefix MEXs

长度为n的数组a,非降序
ai<=i

构造数组b
对于任意i,ai=mex(b1,b2,…bi)
无解输出-1

如果mex已经等于ai了,那么就需要添加大于mex的且后缀没出现过的数,为后续产生更大的mex创造机会,,否则就添加mex

trick:

mex常用的技巧之前总结过了,

一个是看后缀是否出现,一个是

while(s.count(mex)) mex++;
#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,mmp;
	for(int i=1;i<=n;i++) cin>>a[i],mp[a[i]]++;
	set<int>s;
	int mex=0;
	int x=0;
	vector<int>ans;
	for(int i=1;i<=n;i++){
		if(mex==a[i]){
			while(mmp[x]!=mp[x]) x++;
			ans.push_back(x);
			s.insert(x);
			x++;
		}
		else{
			ans.push_back(mex);
			s.insert(mex);
			while(s.count(mex)) mex++;
			x=max(x,mex+1);
		}
		mmp[a[i]]++;
	}
	s.clear();
	mex=0;
	for(int i=0;i<(int)ans.size();i++){
		s.insert(ans[i]);
		while(s.count(mex)) mex++;
		if(mex!=a[i+1]){
			cout<<-1<<endl;
			return;
		}
	}
	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;
}

11.A. Doremy’s IQ

共n场竞赛,第i场难度为ai
Doremy初始智商为q,如果当前智商小于ai,那么可以参加这场比赛并且智商不变,否则可以参加但是智商减1或者选择不参加,智商不变
当智商减为0时,不能参加比赛
问最多参加多少场比赛

贪心
从前往后,首先,智商大于ai肯定参加,智商x如果小于ai,那么看后面小于等于x-1的个数cnt1和后面小于等于x的个数cnt2,如果cnt2>=cnt1+1,那么就不参加当前比赛,否则参加当前按比赛
用树状数组+离散化
这个策略的本质就是如果选的话不影响后面的选取就选,否则就不选,这样不是最优的,这样只能算保守,这样的问题在于虽然选这个会影响后面的选取,但是完全可以通过消耗q来将后面的全部选掉
所以,想到拐点
如果对于某个q,如果有不止一个大于q的,那么晚消耗q肯定比先消耗q优,所以将所有比赛分为两部分,每个点作为一个拐点,前半部分不消耗q,后半部分一直消耗q,然后取最优的,但可以继续优化思路,肯定让q最终刚好全部消耗完是最优的,所以从后往前,令qq为0,从后往前推,直到qq等于q

trick:

1.贪心,有可能想到的并不是最优策策略,可能想到了一个保守策略,觉得最优了,比如说有q值,通过消耗q值来执行操作,那么可能q值消耗刚好为0是最优的

2.贪心之拐点法,作为一个参考方向

#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,q;
void solve() {
	cin>>n>>q;
	for(int i=1;i<=n;i++) cin>>a[i],ans[i]=0;
	int qq=0;
	int idx=1;
	for(int i=n;i>=1;i--){
		if(a[i]>qq) qq++;
		if(qq==q) {
			idx=i;
			break;
		}
	}
//	cout<<idx<<endl;
	for(int i=1;i<=idx-1;i++){
		if(a[i]<=q) ans[i]=1;
	}
	for(int i=idx;i<=n;i++) ans[i]=1;
	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;
}

12.D. Grid-00100

构造一个n * n的01矩阵,使得1的个数刚好等于k
记01矩阵为A,使得f(A)最小
f:(最大的行-最小的行)2+(最大的列-最小的列)2
行最大:行中所有数相加最大
一定有解

肯定要平均,行平均,列平均
平均分配问题,同时保证行分配平均,列分配平均,斜着填
按照离散数学那样,照对角线填
比如n=4
(1,1)->(2,2)->(3,3)->(4,4)
(1,4)->(2,1)->(3,2)->(4,3)
(1,3)->(2,4)->(3,1)->(4,2)
(1,2)->(2,3)->(3,4)->(4,1)
可以发现,让行为1,2,…n,让列循环右移

trick:

1.矩阵填数问题,先想好怎么填,然后在草稿纸中将填数顺序记录下来,根据横纵坐标变化规律写代码

2.矩阵循环右移写法(本质是+1取模,借助循环里其中一维++来实现)

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

13.D. Say No to Palindromes

长度为n的字符串
beautiful:不包含一个长度大于等于2的回文子串
操作:将任意一个字符变为a,b,c中的一个
cost:将字符串边beauticul的最小操作次数
对于m次询问,回答子串[l,r]的cost

要想区间[l,r]beautiful,必须是前三个为abc的全排列,然后后面一直循环,abc的全排列一共就6种,所以就6种情况,只要前3个定死,那么后面就定死,不一样的都得修改

但是求前缀和的话,考虑abc这种排列,比如说第一个区间[1,n]那么前三个为abc然后一直循环,不一样的就计数++,然后如果后面询问[5,10],然后考虑abc这种排列,那么[5,10]为abcabc,但是再往前反推,[1,10]为cabcabcabc,并不是一开始预处理的abc这种情况,妙就妙在这里,这样并没有关系,因为我们是6种情况取最优,不管怎样,都包含了所有情况

这题很妙,当作前缀和题的一个案例

trick:

多次询问不同的区间[l,r],考虑预处理前缀和

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int n,m;
int f[N][6];
string s;
string t[6]={"abc","acb","bac","bca","cab","cba"};
void solve() {
	memset(f,0,sizeof f);
	cin>>n>>m;
	cin>>s;
	s=' '+s;
	for(int i=1;i<=n;i++){
		int k=(i-1)%3;
		for(int j=0;j<6;j++){
			f[i][j]+=f[i-1][j]+(s[i]!=t[j][k]);
		}
	}
	while(m--){
		int l,r;
		cin>>l>>r;
		int ans=2e9;
		for(int j=0;j<6;j++) ans=min(ans,f[r][j]-f[l-1][j]);
		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;
}

14.C. Chocolate Bunny

交互题
通过问问题,来猜测长度为n的全排列
最多问2 * n 次

利用%前后大小关系,小的%大的=小的
所以先找到最大的n,然后其它模n都等于自身,似乎不好找
可以一个数模另一个数,然后再反过来一次,这样肯定可以确定一个数

trick:

1.%前后大小关系,小的%大的=小的

2.一个数模另一个数,然后再反过来一次,这样肯定可以确定一个数

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

15.D. Min Cost String

构造一个长度为n的字符串,只包括前k个小写字母
代价:si=sj&&si+1=sj+1的数目
要求代价最小

这题就是排列组合,使得相邻两个一起出现的次数越少越好
长度长了肯定会花费代价,所以造一个极长的一个周期,没填完才循环
比如k为4
a ab ac ad b bc bd c cd d 这样为一循环

trick:

如何把一个周期循环放入n个数中:

先将一个周期放入vector中,然后利用循环右移取周期里的数

for(int i=0;i<n;i++){
		cout<<(char)(ans[i%(int)ans.size()]+'a');
	}
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,k;
void solve() {
	cin>>n>>k;
	vector<int>ans;
	for(int i=0;i<k;i++){
		ans.push_back(i);
		for(int j=i+1;j<k;j++){
			ans.push_back(i);
			ans.push_back(j);
		}
	}
	for(int i=0;i<n;i++){
		cout<<(char)(ans[i%(int)ans.size()]+'a');
	}
}
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. Dima and a Bad XOR

n * m的矩阵
每一行选择一个数,使得n个数异或起来大于0,如果无解输出NIE
否则TAK,输出n行,每行选第几列的数

这题很妙,先将每行第一个数异或起来,如果不为0,那么只要其中一行找一个和改行第一个数不一样就行了

异或:x ^ x = 0,只要改掉其中一个数,结果必不为0

当案例

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=510;
int a[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];
		}
	}
	int tmp=0;
	for(int i=1;i<=n;i++){
		tmp^=a[i][1];
	}
	if(tmp){
		cout<<"TAK"<<endl;
		for(int i=1;i<=n;i++) cout<<1<<' ';
		cout<<endl;
		return;
	}
	for(int i=1;i<=n;i++){
		for(int j=2;j<=m;j++){
			if(a[i][j]!=a[i][1]){
				cout<<"TAK"<<endl;
				for(int k=1;k<=i-1;k++) cout<<1<<' ';
				cout<<j<<' ';
				for(int k=i+1;k<=n;k++) cout<<1<<' ';
				cout<<endl;
				return;
			}
		}
	}
	cout<<"NIE"<<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.D. Vupsen, Pupsen and 0

构造一个数组b,不包含数字0,使得所有ai * bi 加起来等于0,一定有解

两两抵消为0

trick:

构造题,可以先构造一个循环节(类似),然后再按照此法一直填写

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

18.C. Remove Adjacent

给定一个字符串s,问最多能删除几个字符
操作:如果相邻字符中ASCII码小1,那么可以删掉该字符

a删不掉,b只能靠a删,c只能靠b删
数据小,直接暴力,字符从大到小删

trick:

a删不掉,b只能靠a删,c只能靠b删,像这种话往往是解题的关键,类似的分析可以写下来,又比如求字典序最小,先看第一位,然后再看第二位,一位一位看

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
string s;
void solve() {
	cin>>n;
	cin>>s;
	int ans=0;
	for(int i=26;i>=1;i--){
		char ch=(char)('a'+i-1);
		bool ok=true;
		while(ok){
			ok=false;
			string tmp;
			for(int i=0;i<(int)s.size();i++){
				if(s[i]==ch){
					if((i-1>=0&&s[i-1]==(char)(ch-1))||(i+1<(int)s.size()&&s[i+1]==(char)(ch-1))){
						ans++;
						ok=true;
						continue;
					}
					else{
						tmp+=s[i];
					}
				}
				else{
					tmp+=s[i];
				}
			}
			s=tmp;
		}
	}
	cout<<ans<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

19.C. Basic Diplomacy

玩m天游戏,每天在n个人中选择一个人作为队友,每个人被选择不能超过m/2次

每天选当天可选择中使用次数最少的那个
m/2比较特殊,只要不是当天只能选择一个人导致超过m/2次,其它都YES

#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 ans[N];
int n,m;
void solve() {
	cin>>n>>m;
	vector<vector<int>>e(m);
	map<int,int>mp;
	bool ok=true;
	for(int i=0;i<m;i++){
		int k;
		cin>>k;
		for(int j=0;j<k;j++){
			int x;
			cin>>x;
			e[i].push_back(x);
		}
		if(k==1){
			mp[e[i][0]]++;
			if(mp[e[i][0]]>(m+1)/2){
				ok=false;
			}
		}
	}
	if(!ok){
		cout<<"NO"<<endl;
		return;
	}
	for(int i=0;i<m;i++){
		if(e[i].size()==1){
			ans[i]=e[i][0];
			continue;
		}
		int mini=e[i][0];
		for(int j=1;j<(int)e[i].size();j++){
			if(mp[mini]>mp[e[i][j]]){
				mini=e[i][j];
			}
		}
		ans[i]=mini;
		mp[mini]++;
	}
	cout<<"YES"<<endl;
	for(int i=0;i<m;i++) cout<<ans[i]<<' ';
	cout<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

20.A. Vitaly and Strings

找到字典序比s大,比t小的字符串

基本上是两种情况,要么是s+1,要么是t-1,要么特判一下

azzz
caaa
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
string s,t;
void solve() {
	cin>>s>>t;
	int len=s.size();
	string tmp1;
	for(int i=0;i<len-1;i++) tmp1+=t[i];
	tmp1+=(char)(t[len-1]-1);
	string tmp2;
	for(int i=0;i<len-1;i++) tmp2+=s[i];
	tmp2+=(char)(s[len-1]+1);
	bool ok=true;
	for(int i=0;i<len;i++){
		if(tmp1[i]<'a'||tmp1[i]>'z'){
			ok=false;
			break;
		}
	}
	if(tmp1>s&&tmp2<t&&ok){
		cout<<tmp1<<endl;
		return;
	}
	ok=true;
	for(int i=0;i<len;i++){
		if(tmp2[i]<'a'||tmp2[i]>'z'){
			ok=false;
			break;
		}
	}
	if(tmp2>s&&tmp2<t&&ok){
		cout<<tmp2<<endl;
		return;
	}
	string tmp;
	for(int i=0;i<len;i++){
		if(t[i]-s[i]>1) tmp+=(char)(t[i]-1);
		else tmp+=t[i];
	}
	if(tmp>s&&tmp<t){
		cout<<tmp<<endl;
		return;
	}
	cout<<"No such string"<<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.D. Secret Santa

一共有n个员工
ai表示第i个员工想为ai准备礼物
构造一个序列b,bi表示第i个员工为bi准备礼物,使得越多人满足越好
bi所有数都必须不同

先把可以匹配的匹配了,那些第一次出现过的数字都可以直接确定
然后对于那些没有确定的,我们只需要保证pi!=i即可,可以先随便填入,然后对于pi=i的,进行移位即可,如果有多个pi=i,可以进行移位,但如果只有一个pi=i,那么只需要和匹配对象为a[i]的换

trick:

先确定能够确定的位置填数,然后其它位置可以先随便填入,然后再调整,进行移位或交换操作

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N],b[N];
int n;
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	map<int,int>mp,pos;
	int cnt=0;
	for(int i=1;i<=n;i++){
		if(!mp[a[i]]) b[i]=a[i],mp[a[i]]=1,cnt++;
		else pos[i]=1;//没有确定的位置
	}
	set<int>s;
	for(int i=1;i<=n;i++){
		if(!mp[i]) s.insert(i);
	}
	for(auto v:pos){
		b[v.first]=*s.begin();
		s.erase(s.begin());
	}
	vector<int>ans;
	for(auto v:mp){
		if(b[v.first]==v.first) ans.push_back(v.first);
	}
//	for(int i=1;i<=n;i++) cout<<b[i]<<' ';
//	cout<<endl;
//	cout<<ans.size()<<endl;
//	for(auto v:ans) cout<<v<<' ';
//	cout<<endl;
	if(ans.size()==0){
		cout<<cnt<<endl;
		for(int i=1;i<=n;i++) cout<<b[i]<<' ';
		cout<<endl;
		return;
	}
	if(ans.size()==1){
		for(int i=1;i<=n;i++){
			if(i==ans[0]) continue;
			if(b[i]==a[ans[0]]){
				swap(b[i],b[ans[0]]);
			}
		}
	}
	else{
		int tmp=b[ans[0]];
		for(int i=0;i<(int)ans.size()-1;i++){
			b[ans[i]]=b[ans[i+1]];
		}
		b[ans[(int)ans.size()-1]]=tmp;
	}
	cout<<cnt<<endl;
	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;
}

22.C. Replacement

长度为n的字符串(由小写字母和句号组成)
操作:将两个连续句号变成一个句号
使得没有没有连续的句号
共m次询问,每次修改一个字符(每次修改会保留),问操作的最小次数

一开始先记录连续句号的区间,并求出最开始操作的最小次数
然后,每次操作进行修改区间,并记录操作次数的变化

一开始写的,细节很多,特别是二分的边界,最后还是没调对,吃力不讨好

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=3e5+10;
int rr[N];//区间右端点
int n,m;
string s;
void solve() {
	cin>>n>>m;
	cin>>s;
	set<int>ans;//记录区间左端点
	bool ok=false;
	int l;
	for(int i=0;i<n;i++){
		if(!ok&&s[i]=='.'){
			l=i;
			ans.insert(l);
			ok=true;
		}
		else if(ok&&s[i]!='.'){
			rr[l]=i-1;
			ok=false;
		}
	}
	if(ok) rr[l]=n-1;
	int sum=0;
	for(auto v:ans){//计算最开始的操作次数
		int l=v,r=rr[l];
		int len=r-l+1;
		sum+=len-1;
	}
//	cout<<sum<<endl;
	while(m--){
		int x;
		char c;
		cin>>x>>c;
		x--;
		auto it=ans.upper_bound(x);//找到大于x的第一个左端点
		if(it==ans.end()){//找不到大于x的第一个左端点,即x后面没有句号
			if(s[x]=='.'){
				if(c=='.'){
					cout<<sum<<endl;
					continue;
				}
				else{
					it--;
					int l=*it,r=rr[l];
					if(l==r){
						ans.erase(it);
						cout<<sum<<endl;
						continue;
					}
					else{
						rr[l]=r-1;
						sum--;
					}
				}
			}
			else{
				if(c!='.'){
					cout<<sum<<endl;
					continue;
				}
				else{
					if(s[x-1]=='.'){
						sum++;
						it--;
						int l=*it;
						rr[l]=x;
					}
					else{
						ans.insert(x);
					}
				}
			}
			cout<<sum<<endl;
			continue;
		}
		if(it!=ans.begin()) {
			it--;//找到小于等于x的第一个左端点
			int l=*it,r=rr[l];//区间
			if(x>r){//不在区间中
				if(c!='.'){
					cout<<sum<<endl;
					continue;
				}
				else{
					s[x]='.';
					if(s[x-1]=='.'&&s[x+1]=='.'&&it!=(--ans.end())){
						sum+=2;
						it++;
						rr[l]=rr[*it];
						ans.erase(it);
					}
					else if(s[x-1]=='.'){
						sum++;
						rr[l]=x;
					}
					else if(s[x+1]=='.'&&it!=(--ans.end())){
						sum++;
						it++;
						rr[x]=rr[*it];
						ans.erase(it);
						ans.insert(x);
					}
				}
			}
			else {//在区间中,x位置原本为句号
				if(c=='.'){
					cout<<sum<<endl;
					continue;
				}
				else{
					if(l==r){
						ans.erase(l);
						cout<<sum<<endl;
						continue;
					}
					else{
						if(x==l||x==r){
							sum--;
							if(x==l){
								ans.erase(l);
								if(l+1<=r) {
									ans.insert(l+1);
									rr[l+1]=r;
								}
							}
							else {
								if(l<=r-1){
									rr[l]=r-1;
								}
							}
						}
						else{
							sum-=2;
							rr[x+1]=rr[l];	
							rr[l]=x-1;
							ans.insert(x+1);
						}
					}
				}
			}
		}
		else{//在第一个句号的前面
			if(c!='.'){
				cout<<sum<<endl;
				continue;
			}
			else{
				s[x]='.';
				if(s[x+1]=='.'){
					rr[x]=rr[x+1];
					ans.erase(x+1);
					ans.insert(x);
				}
				else{
					ans.insert(x);
					rr[x]=x;
				}
			}
		}
		cout<<sum<<endl;
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

参考Codeforces Round #316 (Div. 2) - NotNight - 博客园 (cnblogs.com)

trick:

如果连续的两个点产生一个贡献,那么可以转化为点的个数减去段数,只要一直维护点的个数和段数即可

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=3e5+10;
char s[N];
int n,m;
void solve() {
	cin>>n>>m;
	cin>>(s+1);
	int cnt=0,num=0;//cnt表示一共有几段,num表示一共有几个点
	for(int i=1;i<=n;i++){
		if(s[i-1]!='.'&&s[i]=='.') cnt++;
		if(s[i]=='.') num++;
	}
	while(m--){
		int x;
		char c;
		cin>>x>>c;
		if(c=='.'){
			if(s[x]!='.'){
				num++;//点的个数+1
				if(s[x-1]=='.'&&s[x+1]=='.') cnt--;
				else if(s[x-1]!='.'&&s[x+1]!='.') cnt++;
			}
			s[x]=c;
		}
		else{
			if(s[x]=='.'){
				num--;
				if(s[x-1]=='.'&&s[x+1]=='.') cnt++;
				else if(s[x-1]!='.'&&s[x+1]!='.') cnt--;
			}
			s[x]=c;
		}
		cout<<num-cnt<<endl;
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

23.D. Shocking Arrangement

参考【CF1798D】Shocking Arrangement - 洛谷专栏 (luogu.com.cn)

长度为n的数组a,所有数的和为0
记最大值为max,最小值为min,均是确定的
重新排列数组a,使得最大的sum[r]-sum[l-1]小于max-min
无解输出No

最大最小,次大次小交替
这样不对,对于一个很小的负数,应该用很多正数去抵消
维护前缀和,如果为正数,那么填入负数,如果为负数,那么填入正数,因为这样任意前缀和超不过肯定在[min,max]之间,那么前缀和减前缀和肯定超不过max-min

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=3e5+10;
int dp1[N],dp2[N];
int n;
void solve() {
	cin>>n;
	deque<int>q1,q2;
	int ma=-2e9,mi=2e9;
	for(int i=1;i<=n;i++){
		int x;
		cin>>x;
		ma=max(ma,x);
		mi=min(mi,x);
		if(x>=0) q1.push_back(x);
		else q2.push_back(x);
	}
	vector<int>ans;
	int sum=0;
	while(q1.size()&&q2.size()){
		if(sum>=0){
			sum+=q2.front();
			ans.push_back(q2.front());
			q2.pop_front();
		}
		else{
			sum+=q1.front();
			ans.push_back(q1.front());
			q1.pop_front();
		}
	}
	while(q1.size()){
		ans.push_back(q1.front());
		q1.pop_front();
	}
	while(q2.size()){
		ans.push_back(q2.front());
		q2.pop_front();
	}
	if(ma==mi) cout<<"No"<<endl;
	else{
		cout<<"Yes"<<endl;
		for(int i=0;i<(int)ans.size();i++) cout<<ans[i]<<' ';
		cout<<endl;
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

24.D. Fixed Point Guessing

长度为n的数组a(n为奇数)
一开始按照1,2,3,…n的顺序排列
评委选择了(n-1)/2对元素进行交换,剩下一个元素没有交换,目标是找到这个元素

最多问15次,每次问区间[l,r]是哪些元素,只能得到有哪些,顺序无法得知

参考CF1698D Fixed Point Guessing - 洛谷专栏 (luogu.com.cn)

当所问区间中原本就在该区间的个数为奇数时,说明答案在该qu’j

trick:

交互题可有根据询问次数猜测做法,比如这题最多问15次,由于 2 15 2^{15} 215大概1e4的级别,所以猜测二分

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
	cin>>n;
	int l=1,r=n;
	while(l<r){
		int mid=(l+r)/2;
		cout<<"? "<<l<<' '<<mid<<endl;
		cout.flush();
		int cnt=0;
		for(int i=l;i<=mid;i++){
			int x;
			cin>>x;
			if(x>=l&&x<=mid) cnt++;
		}
		if(cnt%2) r=mid;
		else l=mid+1;
	}
	cout<<"! "<<l<<endl;
	cout.flush();
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

25.A. Sum in the tree

参考题解 CF1099D 【Sum in the tree】 - 洛谷专栏 (luogu.com.cn)

n个顶点的树

直接让那些被删除的节点的a值为0就好,不是最优的,因为如果在交叉口,那么可以赋值,为多个分支作出前缀和的贡献,解决办法就是让被删除的节点的s值等于它的子树中最小的s
不能用子树,而是从上往下仅仅取和儿子节点的小的s值
无解的情况就是节点x的儿子子节点y到根节点的距离比x到根节点的距离小

trick:
1.熟悉对于dfs遍历的写法

2.如果在交叉口会对多个分支作出前缀和的贡献,要使得所有节点权值之和最小,解决办法是让该节点等于它的儿子节点中小的那个

不用看子树,每个都是父子之间的联系,要判无解的话,只要有父子不满足即可

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N = 1e5 + 10;
int p[N], s[N];
int a[N];
int n;
map<int,vector<int>>e;
int ans;
void dfs1(int u,int fa){
	s[fa]=min(s[fa],s[u]);
	for(auto v:e[u]){
		if(v==fa) continue;
		dfs1(v,u);
	}
}
void dfs2(int u,int fa){
	if(s[u]<s[fa]){
		cout<<-1<<endl;
		exit(0);
	}
	if(s[u]!=2e9){
		ans+=s[u]-s[fa];
	}
	for(auto v:e[u]){
		if(v==fa) continue;
		dfs2(v,u);
	}
}
void solve() {
	cin >> n;
	for (int i = 2; i <= n; i++){
		cin>>p[i];
		e[p[i]].push_back(i);
	}
	for (int i = 1; i <= n; i++){
		cin>>s[i];
		if(s[i]==-1) s[i]=2e9;
	}
	dfs1(1,-1);
	ans=0;
	dfs2(1,-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;
}

26.A. The Very Beautiful Blanket

构造一个n * m的矩阵,非负整数
使得对于任意一个4 * 4的子矩阵,左上角的2 * 2矩阵的异或和等于右下角的2 * 2矩阵的异或和,左下角的2 * 2矩阵的异或和等于右上角的2*2矩阵的异或和
同时不同数字最多
一定有解

异或有一个性质:0和1异或为1,2和3异或为1…

但是这样的话,第一列和第二列可以这样异或为1,但是第二列和第三列就不行了,不可行

参考The Very Beautiful Blanket 题解 - 洛谷专栏 (luogu.com.cn)

可以让任意的2 * 2的异或和都为0

trick:

1.关于异或和相等,可以让异或和均为0,通过相同抵消,若要让每个数尽量不同,可以乘或异或上一个2的幂次(很大的数)

2.构造矩阵,可以让a[i] [j]和i,j联系在一起

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=210;
int a[N][N];
int n,m;
void solve() {
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			a[i][j]=(i*(1ll<<9))^j;
		}
	}
	cout<<n*m<<endl;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cout<<a[i][j]<<' ';
		}
		cout<<endl;
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

27.D. Walking Between Houses

一共有n间房子,编号为1到n
一开始位于1
一共走k次,一共要走s个单位,每次到的房子都要与当前房子不同
YES,NO

用总距离s / 次数 得到平均每次走多少距离,记为cnt,如果大于n-1或者小于1,那么无解,如果等于n-1,但是cnt * k小于s,无解
否则,平均分

主要运用了平均分的技巧

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,k,s;
void solve() {
	cin>>n>>k>>s;
	int cnt=s/k;
	if(cnt>n-1||cnt<1){
		cout<<"NO"<<endl;
		return;
	}
	if(cnt==n-1){
		if(cnt*k<s){
			cout<<"NO"<<endl;
			return;
		}
	}
	//平均分
	int res=s%k;//res个cnt+1,k-res个cnt
	vector<int>ans;
	int flag=1;
	int path=1;
	for(int i=0;i<res;i++){
		if(flag==1) path+=cnt+1;
		else path-=cnt+1;
		ans.push_back(path);
		flag^=1;
	}
	for(int i=0;i<k-res;i++){
		if(flag==1) path+=cnt;
		else path-=cnt;
		ans.push_back(path);
		flag^=1;
	}
	cout<<"YES"<<endl;
	for(int i=0;i<(int)ans.size();i++) cout<<ans[i]<<' ';
	cout<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

28.C. Tennis Championship

一共有n名参赛选手
两两对局,输了立即淘汰,两个对局的人要求是对局数不能超过1
问比赛获胜者最多能参加多少场比赛

n达1e18,肯定是找规律了
发现n从2开始,数一个,答案为1,然后往下数两个,答案为2,再往下数三个,答案为3,…
二分找到小于等于n的第一个(1+2+3+…+k)+1=(1+k)* k / 2 +1
如果n刚好到(1+2+3+…+k)+ 1,那么为k个,如果n大一些的话,那么答案为k+1
可惜找错规律了,样本太少了,就手玩了小的数

要想参加n场比赛,最少需要至少参加n-1场的人数+至少参加n-2场的人数
其中参加n-1场的和参加n-2场的互不干扰,因为双方都是独立各自产出一个胜者,再pk
所以就是斐波那契数列,阶乘级别的

trick:

递归的思想,它的子问题的答案是固定的,且分解成的两个子问题互不干扰

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
	cin>>n;
	int x=1,y=2,z=3;
	int ans=1;
	while(z<=n){
		x=y;
		y=z;
		z=x+y;
		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;
}

29.D. Same Count One

n * m的01矩阵
操作:选择两行,交换对应的两个元素
问最少几次使得每行1的个数相等

模拟题
统计1的总个数,除以n,就是每行1的个数,记为ave,如果不能整除,则无解
然后分别记录1的个数大于ave的行和小于ave的行,放入set1和set2中
然后大于ave的行去和小于ave的行交换,当大于ave的行刚好等于ave,那么就从set1中去除,同理,小于ave的行刚好等于ave时,就从set2中去除
但是这样超时了,反复的从第一列开始遍历

//超时
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
int n, m;
struct node{
	int x,y,z;
};
void solve() {
	cin >> n >> m;
	int sum = 0;
	vector<vector<int>>a(n + 1, vector<int>(m + 1));
	set<PII>s1, s2;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> a[i][j];
			if (a[i][j] == 1) sum++;
		}
	}
	if (sum % n) {
		cout << -1 << endl;
		return;
	}
	int ave=sum/n;
	for (int i = 1; i <= n; i++) {
		int cnt = 0;
		for (int j = 1; j <= m; j++) {
			if(a[i][j]==1) cnt++;
		}
		if(cnt>ave) s1.insert({i,cnt});
		else if(cnt<ave) s2.insert({i,cnt});
	}
	vector<node>ans;
	while(s1.size()&&s2.size()){
		auto t1=*s1.begin(),t2=*s2.begin();
		int line1=t1.first,cnt1=t2.second,line2=t2.first,cnt2=t2.second;
		for(int j=1;j<=m;j++){
			if(a[line1][j]==1&&a[line2][j]==0){
				swap(a[line1][j],a[line2][j]);
				ans.push_back({line1,line2,j});
				cnt1--,cnt2++;
				if(cnt1==ave&&cnt2==ave){
					s1.erase(s1.begin());
					s2.erase(s2.begin());
					break;
				}
				else if(cnt1==ave){
					s1.erase(s1.begin());
					s2.erase(s2.begin());
					s2.insert({line2,cnt2});
					break;
				}
				else if(cnt2==ave){
					s1.erase(s1.begin());
					s2.erase(s2.begin());
					s1.insert({line1,cnt1});
					break;
				}
			}
		}
	}
	cout<<ans.size()<<endl;
	for(auto v:ans) cout<<v.x<<' '<<v.y<<' '<<v.z<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
	cin >> t;
	while (t--) {
		solve();
	}
	return 0;
}

可以改进一下,对于每一列,记录大于ave且a值为1的行以及小于ave且a值为0的行,分为两个集合,以长度短的为标准,两两交换

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

trick:

1.一开始先想一个思路,出现问题,对症下药,比如超时了,反复的从第一列开始遍历,那么就对此改进,枚举列,对于某一列进行行上的操作

2.如果操作是对某一列的两行进行交换,那么就枚举列,让列不动

有很多题目都用固定d,比如固定长度,固定最小值,固定答案然后check

#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
int n, m;
struct node{
	int x,y,z;
};
void solve() {
	cin >> n >> m;
	int sum = 0;
	vector<vector<int>>a(n + 1, vector<int>(m + 1));
	map<int,int>mp;//mp[i]表示第i行有几个1
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> a[i][j];
			if (a[i][j] == 1) sum++,mp[i]++;
		}
	}
	if (sum % n) {
		cout << -1 << endl;
		return;
	}
	int ave=sum/n;
	vector<node>ans;
	for(int j=1;j<=m;j++){
		vector<int>s1,s2;
		for(int i=1;i<=n;i++){
			if(mp[i]>ave&&a[i][j]==1) s1.push_back(i);
			else if(mp[i]<ave&&a[i][j]==0) s2.push_back(i);
		}
		int len=min(s1.size(),s2.size());
		for(int i=0;i<len;i++){
			mp[s1[i]]--;
			mp[s2[i]]++;
			ans.push_back({s1[i],s2[i],j});
		}
	}
	cout<<ans.size()<<endl;
	for(auto v:ans) cout<<v.x<<' '<<v.y<<' '<<v.z<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
	cin >> t;
	while (t--) {
		solve();
	}
	return 0;
}

30.D. Insert a Progression

长度为n的序列
将1到x共x个数插入到序列中
得分:相邻元素差的绝对值之和
问最少得分

对于两个数,比如是说
2和10
它们的差值为8

如果在它们之间插入一个1,那么差值变为1+9=10,变大
如果在它们之间插入一个11,那么差值变为9+1=10,变大
所以应该插入数值在它们之间的数,比如说7,差值仍为8,同时按升序的顺序插入,当然如果是10 和 2,那么就按降序的顺序插入

所以最终就是大于序列中最大值的先不放进去,小于最小值的先放进去(其实就一个最大值x和一个最小值1就可,原因在于1 2 3 4和1 4差值是一样的)
其它全部放进去和最开始的差值没变化
然后想办法把1放在序列的最小值旁边,多的差值是2*(min-1)或者min-1,把x放在序列的最大值旁边,多的差值是2*(x-max)或者x-max
至于是否乘2,取决于最小值是否在边上,最大值是否在边上
当然,不一定非要放在最值旁边,因为有可能要乘2,直接放在边上就不用乘2,两者取min就可

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

31.C. Li Hua and Chess

交互题
n*m的棋盘,有一个王放在棋盘的某个位置
目标猜出王的位置
最多问3次
问 (x,y),得到王到(x,y)的最小步数

做过类似的题,这个更简单些
第一次问(1,1)得到王所在的对角线(左下右上方向的对角线)
第二次问(1,n)得到左上右下方向的对角线
然后两条对角线交于一点
所以问两次就够了
问题在于如何计算交点
可以先求出第一条对角线右上的顶点坐标,然后求出它对应第二条对角线的第几条,然后再x++,y–找到交点,注意每次x++,y–是经过两条对角线
然后写下了如下错误代码

原来又读错题了,国王还可以斜着走,以为只能上下左右
题目一定要慢慢读,不然一个小时做一个假题
问(1,1)和(n,m),得到两个正方形,得到两个交点,然后问一个确定另一个

参考CF1797C Li Hua and Chess - 洛谷专栏 (luogu.com.cn)

trick:

1.题目一定要慢慢读,一个字一个字读,不可图快

2.三种距离:

欧式距离、曼哈顿距离、切比雪夫距离

欧式距离:我们平常所用的,对于某个顶点,与它欧式距离相同的轨迹是一个圆

曼哈顿距离:就是只能上下左右一格一格移动,对于某个定点,与它曼哈顿距离相同的轨迹是一条对角线

切比雪夫距离:就是不仅可以上下左右一格一格移动,还可以斜着一格一格移动,对于某个定点,与它切比雪夫距离相同的轨迹是一个正方形

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,m;
void ask(int x,int y){
	cout<<"? "<<x<<' '<<y<<endl;
	cout.flush();
}
void answer(int x,int y){
	cout<<"! "<<x<<' '<<y<<endl;
	cout.flush();
}
void solve() {
	cin>>n>>m;
	ask(1,1);
	int ans;
	cin>>ans;
	if(ans==0) {
		answer(1,1);
		return;
	}
	int line1=min(n,1+ans),col1=min(m,1+ans);
	ask(n,m);
	cin>>ans;
	if(ans==0) {
		answer(n,m);
		return;
	}
	int line2=max(1ll,n-ans),col2=max(1ll,m-ans);
	//得到两个交点
	int x1=line1,y1=col2;
	int x2=line2,y2=col1;
	if(x1==x2&&y1==y2) answer(x1,y1);
	else if(x1!=x2&&y1!=y2){
		if(x1>=1&&x1<=n&&y1>=1&&y1<=m){
			ask(x1,y1);
			cin>>ans;
			if(ans==0) answer(x1,y1);
			else answer(x2,y2);
		}
		else answer(x2,y2);
	}
	else{
		if(x1==x2){
			if(y1>y2) swap(y1,y2);
			if(x1>=1&&x1<=n&&y1>=1&&y1<=m){
				ask(x1,y1);
				cin>>ans;
				if(ans==0) answer(x1,y1);
				else answer(x1,y1+ans);
			}
			else{
				ask(x2,y2);
				cin>>ans;
				if(ans==0) answer(x2,y2);
				else answer(x2,y2-ans);
			}
		}
		else if(y1==y2){
			if(x1>x2) swap(x1,x2);
			if(x1>=1&&x1<=n&&y1>=1&&y1<=m){
				ask(x1,y1);
				cin>>ans;
				if(ans==0) answer(x1,y1);
				else answer(x1+ans,y1);
			}
			else{
				ask(x2,y2);
				cin>>ans;
				if(ans==0) answer(x2,y2);
				else answer(x2-ans,y2);
			}
		}
	}
}
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. Heap Operations

一共有n行记录
操作:
1.将给定数字放入堆中
2.获取堆中最小元素的值
3.取出堆中最小元素

记录的顺序混乱了,导致有些操作不合法
要求插入一些记录(可以插入在任何位置),使得所有操作合法
问最少几条记录,并按顺序输出所有记录

数据结构:小根堆
模拟,当遇到不合法操作时,在它前面插入一些记录使得当前操作合法

trick:

1.需要注意的是这些stl自身操作的合法性,要注意判断是否为空

2.将数字(不止一位)转化为字符串

string s=to_string(x);
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
	cin>>n;
	priority_queue<int,vector<int>,greater<int>>q;
	vector<string>ans;
	for(int i=0;i<n;i++){
		string s;
		cin>>s;
		if(s=="insert"){
			int x;
			cin>>x;
			q.push(x);
			string tmp="insert ";
			string t=to_string(x);
			tmp+=t;
			ans.push_back(tmp);
		}
		else if(s=="removeMin"){
			if(q.empty()){
				string tmp="insert 1";
				q.push(1);
				ans.push_back(tmp);
			}
			string tmp="removeMin";
			q.pop();
			ans.push_back(tmp);
		}
		else if(s=="getMin"){
			int x;
			cin>>x;
			if(q.empty()){
				string tmp="insert ";
				string t=to_string(x);
				tmp+=t;
				ans.push_back(tmp);
				q.push(x);
			}
			while(q.top()<x&&q.size()){
				string tmp="removeMin";
				ans.push_back(tmp);
				q.pop();
			}
			if(q.empty()||q.top()!=x){
				string tmp="insert ";
				string t=to_string(x);
				tmp+=t;
				ans.push_back(tmp);
				q.push(x);
			}
			string tmp="getMin ";
			string t=to_string(x);
			tmp+=t;
			ans.push_back(tmp);
		}
	}
	cout<<ans.size()<<endl;
	for(auto v:ans) cout<<v<<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. Sequence Master

给定一个长度为2*n的序列q
求与序列q最接近的好的序列p,只需输出p和q的距离即可
好的:任意一半元素的乘积等于另一半元素的和

根据样例,
可以发现n为1时,将两个数变得一样即可
n为2时,可以全为2
然后第三个样例,发现不知道怎么构造,只能慢慢试,可以发现-1 -1 -1 2,然后进行推广,当n为偶数时,一种构造方式就是-1 -1 -1…n,对于为奇数不适用
另外还有一种万能的构造方法就是全0
检验这几种构造方式,取距离最小的即可

trick:

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

对于两个序列,要使得它们的距离最小,那么同时让它们升序

2.如果不理解样例,那么理解样例就是解题的关键

#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;
	for(int i=1;i<=2*n;i++) cin>>a[i];
	sort(a+1,a+1+2*n);
	if(n==1) cout<<abs(a[1]-a[2])<<endl;
	else if(n==2){
		int ans1=0;
		for(int i=1;i<=2*n;i++) ans1+=abs(a[i]-2);
		int ans2=0;
		for(int i=1;i<=2*n-1;i++) ans2+=abs(a[i]+1);
		ans2+=abs(a[2*n]-n);
		int ans3=0;
		for(int i=1;i<=2*n;i++) ans3+=abs(a[i]);
		cout<<min({ans1,ans2,ans3})<<endl;
	}
	else if(n%2){
		int ans=0;
		for(int i=1;i<=2*n;i++) ans+=abs(a[i]);
		cout<<ans<<endl;
	}
	else{
		int ans1=0;
		for(int i=1;i<=2*n-1;i++) ans1+=abs(a[i]+1);
		ans1+=abs(a[2*n]-n);
		int ans2=0;
		for(int i=1;i<=2*n;i++) ans2+=abs(a[i]);
		cout<<min(ans1,ans2)<<endl;
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

34.A. Sorting Railway Cars

一共有n节车厢,编号为1到n,每节车厢编号不同
但是车厢并不是按照编号从小到大顺序排列,而是打乱顺序
操作:将一节车厢放到头或者尾
问最少几次操作,可以使得车厢按照编号从小到大顺序排列

最后的结果肯定是1,2,3,…n
如果一个数和比它小1的数逆序,那么可以把它放到最后面,按照从小到大的顺序
或者
如果一个数和比它大1的数逆序,那么就把它放在最前面,按照从大到小的顺序
预处理每个数的位置

然后错了,难道不止这两种
发现其实只要找到最长上升子序列,然后这些数不动,其它数动就行
但是最长上升子序列为O(n^2)
再次进行优化,可以求挨个最长上升子序列

证明见CF605A - 洛谷专栏 (luogu.com.cn)

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[N];
int dp[N];
int n;
void solve() {
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) dp[a[i]]=dp[a[i]-1]+1;
	int ans=0;
	for(int i=1;i<=n;i++) ans=max(ans,dp[i]);
	cout<<n-ans<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

35.C. Sequence Transformation

初始1到n共n个整数
操作:将剩余所有数的最大公约数放到答案中,并删除任意一个元素
操作n次
问最终字典序最大是多少

当n小于等于3时,特判
相差为1的数,最大公约数肯定是1
如果不把所有奇数删掉,那么肯定最大公约数为1,所以先把所有奇数删掉,然后,只剩下偶数,最大公约数始终为2,然后最后是最大的偶数
有一点问题
先把除了2的幂次的偶数删掉,然后再从小到大把2的幂次删掉
另外,如果最大的偶数和最大的2的幂次的最大公因数等于最大的2的幂次和次小的2的幂次的最大公因数
又改成先保留2的幂次和最大的2的幂次一直加gcd(最大的2的幂次,次大的2的幂次)
还是错了

类似的题真的很难调,一直wa,可以wa到test 16
很难这种题,关于2的次幂,偶数

参考CodeForces 1059 C.Sequence Transformation (思维)_gcd的最大字典序 codeforces-CSDN博客

tirck:

类似的,关于2的次幂,偶数的,真的很难调

这题每次都可以变成原问题的子问题,就可以利用递归的思想,不一定非得用递归,可以用while循环

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
	cin>>n;
	int t=1;
	while(n>3){
		for(int i=1;i<=n-n/2;i++) cout<<t<<' ';
		n/=2;
		t*=2;
	}
	if(n==1) cout<<t<<endl;
	else if(n==2) cout<<t<<' '<<2*t<<endl;
	if(n==3) cout<<t<<' '<<t<<' '<<3*t<<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.E. Matrix and Shifts

n*n的01矩阵
操作4运算:
1.循环上移所有行
2.循环下移所有行
3.循环左移所有列
4.循环右移所有列

操作次数不限
然后选择任意元素,任意数量,进行01反转,但是一次操作耗费1布尔值
问成为单元矩阵需要的最少布尔值

操作太复杂了,没啥思路

参考CF1660E Matrix and Shifts 题解 - 洛谷专栏 (luogu.com.cn)

trick:

一维数组:

循环右移->将数组在右边再复制一份

矩阵:

循环上移所有行->将矩阵在上面再复制一份
循环下移所有行->将矩阵在下面再复制一份
循环左移所有列->将矩阵在左边再复制一份
循环右移所有列->将矩阵在右边再复制一份

2.对于sum [x] [y],可以写一个函数

int Sum(int x,int y){
 return sum[x][y];
}

这样有什么用呢?比如需要前缀和,然后用到sum [-1] [-1],这样在数组是越界的,但是我们可以这样写

int Sum(int x,int y){
 if(x<0||y<0) return 0;
 return sum[x][y];
}

然后直接用Sum(x,y)代替sum [x] [y]

3.对于01矩阵,想要快速求出对角线上1的个数,那么预处理sum [i] [j]=Sum(i-1,j-1)+a [i] [j];

#include<bits/stdc++.h>
#include<cstdio>
#define endl '\n'
#define int long long
using namespace std;
const int N=2010;
int a[2*N][2*N];
int sum[2*N][2*N];
int n;
int Sum(int x,int y){
	if(x>2*n||y>2*n) return 0;
	if(x<0||y<0) return 0;
	return sum[x][y];
}
void solve() {
	cin>>n;
	int cnt=0;//总共1的个数
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			scanf("%1lld",&a[i][j]);
			cnt+=a[i][j];
		}
	}
	for(int i=0;i<2*n;i++){
		for(int j=0;j<2*n;j++){
			a[i][j]=a[i%n][j%n];
		}
	}
	for(int i=0;i<2*n;i++){
		for(int j=0;j<2*n;j++){
			sum[i][j]=Sum(i-1,j-1)+a[i][j];
		}
	}
	int ans=2e9;
	for(int i=0;i<n+1;i++){
		for(int j=0;j<n+1;j++){
			ans=min(ans,n+cnt-2*(Sum(i+n-1,j+n-1)-Sum(i-1,j-1)));
		}
	}
	cout<<ans<<endl;
}
signed main() {
//	ios::sync_with_stdio(false);
//	cin.tie(0);
//	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

37.D. Umka and a Long Flight

f[i]为斐波那契数列的第i项
阶数为n的斐波那契矩形:高为f[n],宽为f[n+1]

给s[x][y]单元格着色
能否将矩形分解成刚好n+1个正方形,使得着色单元格位于边长为1的正方形,最多有一对边长相等的正方形,每个正方形的边长都是斐波那契数
YES,NO

参考CF 863 (Div. 3) D. Umka and a Long Flight(补题)-CSDN博客

trick:

1.出现数学式子,就列式子,推导

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

2.每次都可以变成原问题的子问题,递归的思想,可以用递归或者while循环

对于从矩形中切割最大正方形的问题就是递归的思想

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

38.C. Equal Frequencies

长度为n的字符串s,由小写字母组成
平衡字符串:每个字符出现的次数一样
构造一个平衡字符串t,尽量接近字符串s,一定有解

参考Codeforces Round #844 (Div.1 + Div.2) CF 1782 A~F 题解 - LegendStane - 博客园 (cnblogs.com)

trick:

1.结果已知,倒推,结果未知,但接近给定串,那么假定结果已知,自己构造不同的结果,然后倒推

如果有很多很多情况,然后在这些情况里取最值,那么就去枚举这些情况,分类需精炼,直接去枚举结果,然后倒推

2.如果想要以某种方式排序,直接自定义sort函数,写个cmp,自定义排序优先级
3.令ci表示字母i在s中的出现次数。对于字母i,如果它是选出的cnt个之一,且cnt≤ci,就把这cnt个i都放到s中字母i出现的对应位置;如果被选了但cnt>ci,就先放ci个,剩下的先不放

写法(就看左半部分):

int use=min(ave,(int)e[ord[j]].size());
for(int k=0;k<use;k++) tmp[e[ord[j]][k]]=ord[j]+'a';
for(int k=0;k<ave-use;k++) left.push_back(ord[j]+'a');
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n;
string s;
map<int,vector<int>>e;
bool cmp(int x,int y){
	return e[x].size()>e[y].size();
}
void solve() {
	cin>>n;
	cin>>s;
	e.clear();
	for(int i=0;i<n;i++) e[s[i]-'a'].push_back(i);
	vector<int>ord;
	for(int i=0;i<26;i++) ord.push_back(i);
	sort(ord.begin(),ord.end(),cmp);
	int ans=2e9;
	string t;
	for(int i=1;i<=26;i++){//枚举字母种类
		if(n%i) continue;
		int ave=n/i;
		string tmp(n,'-');
		string left;
		for(int j=0;j<i;j++){//选数量最多的i种字母
			int use=min(ave,(int)e[ord[j]].size());
			for(int k=0;k<use;k++) tmp[e[ord[j]][k]]=ord[j]+'a';
			for(int k=0;k<ave-use;k++) left.push_back(ord[j]+'a');
		}
		int res=left.size();
		for(int j=0;j<(int)tmp.size();j++){
			if(tmp[j]=='-'){
				tmp[j]=left.back();
				left.pop_back();
			}
		}
		if(res<ans){
			ans=res;
			t=tmp;
		}
	}
	cout<<ans<<endl;
	cout<<t<<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. Slava and tanks

1*n的棋盘
棋盘上分布若干坦克,数量和位置均未知
我们在上空投掷炸弹,坦克第一次收到伤害会移到相邻的一格,第二次受到伤害直接摧毁
问最少投掷几个炸弹,摧毁所有坦克

只要在第i格单元格投掷炸弹,那么下一轮该单元格一定没有坦克
因为坦克会乱跑,为了保证摧毁坦克,得按照某种顺序,就是2,1,3,2,4,3…一共2*(n-1)
当n为2和3时,特判
然后错了,样例是2,4,6,…1,3,5,…,2,4,6…
确实是切实可行的
然后就过了

因为坦克只有 2条命,所以炸 3次必炸死。

以下为轰炸方法:

先炸偶数格的坦克,这时坦克会到奇数格;

然后炸奇数格的坦克,原本在奇数格的坦克就会到偶数格去,这时从偶数格移动过来的坦克就会被摧毁;

最后再炸一次偶数格内的坦克即可。

trick:
相邻一个,想到奇偶

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

40.A. Plate Game

博弈
在a*b的长方形里面放半径为r的圆,不能重叠,谁不能放置谁输
问先手赢还是后手赢

先手如果可以放置在正中间,如果后手可以放置,那么始终对称放置
所以先手第一次可以放就赢 ,不能放就输

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int a,b,r;
void solve() {
	cin>>a>>b>>r;
	if(a>=2*r&&b>=2*r) cout<<"First"<<endl;
	else cout<<"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;
}

41.A. Cows and Sequence

最初序列只包含0
操作:
1.将xi添加到前 ai个元素(加法)
2.在序列末尾添加k
3.删除序列的最后一个元素(当元素个数大于等于2时)

共n次操作
问每次操作后所有数字的平均值

维护总和以及总个数比较简单,但是元素的更新比较难,当删除末尾元素时我们得知道末尾元素是什么
也可以维护,维护以x为右端点,1为左端点的区间加了多少,由于每次只需要末尾元素,所以我们当删除末尾元素时,就将此信息更新到它的前一个

注意,当删除末尾元素时,记得将它所记录的以它为右端点的区间加多少的信息清0

trick:
当出现某个问题时,就聚焦于这个问题进行改进,对症下药

#include <bits/stdc++.h>
#include <cstdio>
#define endl '\n'
//#define int long long
using namespace std;
int n;
void solve() {
	cin >> n;
	double sum = 0;
	map<int, int>mp;
	deque<int>q;
	q.push_back(0);
	while (n--) {
		int op;
		cin >> op;
		if (op == 1) {
			int a, x;
			cin >> a >> x;
			mp[a-1] += x;
			sum += a * x;
		} else if (op == 2) {
			int k;
			cin >> k;
			q.push_back(k);
			sum += k;
		} else if (op == 3) {
			int idx = q.size()-1;
			sum -= q[idx] + mp[idx];
			mp[idx - 1] += mp[idx];
			mp[idx]=0;
			q.pop_back();
		}
		printf("%.10f\n", sum / (int)q.size());
	}
}
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. League of Leesins

长度为n的全排列p(1到n)
然后每连续3个元素作为一个三元组,一共n-2组
将三元组内部乱序,以及将不同的三元组整体乱序
还原出全排列p(一定 有解,不止一个解)

结果已知,根据结果倒推

如果个数为1的话,那么肯定在最两端
如果个数为2的话,那么在第二个位置或者倒数第二个位置
其它数个数均为3
然后就可以一个一个推了

trick:
三个元素组成一个三元组,如何进行标记:

map<PII,vector<int>>e;
e[{a,b}].push_back(c);
e[{b,a}].push_back(c);
//可以通过两个元素快速知道第三个元素 
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
typedef pair<int,int>PII;
struct node{
	int x,y,z;
}q[N];
int p[N];
bool vis[N];
int n;
void solve() {
	cin>>n;
	map<int,int>mp;
	map<PII,vector<int>>e;
	for(int i=0;i<n-2;i++){
		int a,b,c;
		cin>>a>>b>>c;
		mp[a]++,mp[b]++,mp[c]++;
		e[{a,b}].push_back(c);
		e[{b,a}].push_back(c);
		e[{a,c}].push_back(b);
		e[{c,a}].push_back(b);
		e[{b,c}].push_back(a);
		e[{c,b}].push_back(a);
	}
	vector<int>res1,res2;
	for(auto v:mp){
		if(v.second==1) res1.push_back(v.first);
		else if(v.second==2) res2.push_back(v.first);
	}
	p[1]=res1[0];
	if(e[{p[1],res2[0]}].size()) p[2]=res2[0];
	else p[2]=res2[1];
	memset(vis,false,sizeof vis);
	vis[p[1]]=true;
	vis[p[2]]=true;
	for(int i=3;i<=n;i++){
		for(auto v:e[{p[i-1],p[i-2]}]){
			if(!vis[v]){
				p[i]=v;
				vis[v]=true;
				break;
			}
		}
	}
	for(int i=1;i<=n;i++) cout<<p[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;
}

43.B. Bear and Forgotten Tree 3

一棵树
有三个条件:
1.有n个顶点
2.直径为d
3.顶点1和其他顶点之间的最大距离为h

问是否有这样一棵树

如果d+1小于n,则无解
一个条件一个条件满足,先使得高度满足,直接1,2,3,…h+1
然后满足直径,直径d的范围必须在[h,2*h],否则无解
如果d大于h:
只需从根节点1再开一个分支,节点个数为d-h
最后只需要补足n个节点即可,继续开分支就行,剩下的节点都和1相连
如果d等于h:只需要补足n个节点即可,剩下的节点都和2相连

特判d为1的情况

trick:

如果要构造,满足多个条件 ,一个条件一个条件考虑

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

44.B. Code For 1

初始列表只有一个元素n
操作:删除一个大于1的元素,然后在同样的位置插入x/2(下取整),x%2,x/2(下取整)
直到所有元素只有0和1
输出最终列表[l,r]中1的个数

参考「水」CF768B Code For 1 - 洛谷专栏 (luogu.com.cn)

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

trick:
递归层数过多,自己模拟小数据,来找一下规律

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int res[N];
int n,l,r;
void solve() {
	cin>>n>>l>>r;
	if(n==1){
		cout<<1<<endl;
		return;
	}
	int cnt=0;
	res[++cnt]=n%2;
	while(n/2>1){
		n/=2;
		res[++cnt]=n%2;
	}
	n/=2;
	reverse(res+1,res+1+cnt);
	int ans=0;
	for(int i=l;i<=r;i++){
		if(i%2==1) ans+=n;
		else{
			for(int j=cnt;j>0;j--){
				if(i%(1ll<<j)==0){
					ans+=res[j];
					break;
				}
			}
		}
	}
	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;
}

45.C. Anya and Smartphone

一共有n个应用程序,每个屏幕分布k个应用程序,可能最后一个屏幕不足k个应用程序
需要按顺序启动m个应用程序,bi表示第i个应用程序的ID

模拟
利用map
记录并维护ID所在的屏幕
以及记录并维护ID是第几个应用程序

#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,m,k;
void solve() {
	cin>>n>>m>>k;
	map<int,int>mp,mmp;
	int cnt=1;
	int sum=0;
	for(int i=1;i<=n;i++) cin>>a[i],mmp[a[i]]=i;
	for(int i=1;i<=n;i++){
		mp[a[i]]=cnt;
		sum++;
		if(sum==k){
			cnt++;
			sum=0;
		}
	}
	int ans=0;
	while(m--){
		int id;
		cin>>id;
		ans+=mp[id]-1;
		ans++;
		if(mmp[id]==1) continue;
		//更新维护
		int now=mmp[id];//现在的位置
		int pre=now-1;//前一个的位置
		int pre_id=a[pre];//先前的id
		swap(mmp[id],mmp[pre_id]);
		swap(a[now],a[pre]);
		swap(mp[id],mp[pre_id]);
	}
	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;
}

46.C. The Phone Number

构造一个长度为n的全排列(1到n)
使得最长递增子序列的长度和最长递减子序列长度之和最小

试了一下小数据,感觉就是1到n升序,但是错了

看到最长的最小,可以想到平均分,平均分成cnt组,每组n/cnt个每组内部升序,然后组和组之间降序,那么答案为n/cnt+cnt
要使得答案最小,那么就要让cnt为sqrt(n)

或者用小数据打表找规律

void solve() {
	for(n=1;n<=10;n++){
		int ans=2e9;
		vector<int>f;
		for(int i=1;i<=n;i++) a[i]=i;
		do{
			int res1=0,res2=0;
			for(int i=1;i<=n;i++){
				dp1[i]=1;
				for(int j=1;j<=i-1;j++){
					if(a[j]<a[i]) dp1[i]=max(dp1[i],dp1[j]+1);
				}
			}
			for(int i=1;i<=n;i++){
				dp2[i]=1;
				for(int j=1;j<=i-1;j++){
					if(a[j]>a[i]) dp2[i]=max(dp2[i],dp2[j]+1);
				}
			}
			for(int i=1;i<=n;i++) res1=max(res1,dp1[i]);
			for(int i=1;i<=n;i++) res2=max(res2,dp2[i]);
			if(ans>res1+res2){
				ans=res1+res2;
				f.clear();
				for(int i=1;i<=n;i++) f.push_back(a[i]);
			}
		}while(next_permutation(a,a+n));
		cout<<n<<endl;
		cout<<ans<<endl;
		for(auto v:f) cout<<v<<' ';
		cout<<endl;
		cout<<"--------------------"<<endl;
	}
}

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

可以发现确实是这样的规律

trick:

1.最长递增子序列和最长递减子序列,长度最短也是1,单个元素都是满足递增子序列或者递减子序列的

2.最长的最小==>平均分

3.全排列函数next_permutation

int a[]={3,1,2};
sort(a,a+3);
do{
 for(int i=0;i<3;i++) cout<<a[i]<<' ';
 cout<<endl;
}while(next_permutaton(a,a+3));

4.可以用小数据打表找规律

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

一个字符串s(仅由b和w组成)
操作:选择一个分界线,将字符串分成左右两半,然后将左边翻转,将右边翻转
操作次数不限
斑马的长度:b和w交替连续的长度
问能制作的斑马的最大长度

abc | def==>cba | fed

发现逆时针成了一个环,那么就是在这个环上找交替最长的长度,当然长度不能超过n

要想构造一个环,只需将字符串(数组)变成两倍

trick:

1.操作性题目,如果觉得毫无章法,无迹可寻,那么最好的一个方法是自己具象化一个对象,然后按照操作操作一次(就一次),然后看和之前有什么变化联系

2.要想构造一个环,只需将字符串(数组)变成两倍

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

48.C. New Year Book Reading

一共有n本书,wi表示编号为i的书的重量
一共读m天,bi表示第i天要读的书的编号

每次读书,比如读x,需要把它上面的书都抬起来(有重量)
每次读了书x,就把它放在最上面

问最少需要抬多少重量的书,我们可以自定最开始书的放置顺序

既然读了x肯定要把x放在最上面,增加后续拿别的书的总重量,那么我们就要让x上面的总重量越小越好
所以,按照拿书的顺序从上到下放置书,初始顺序确定了
关键是如何计算书x上面的总重量
数据比较小,考虑暴力

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1010;
int w[N],b[N];
int n,m;
void solve() {
	cin>>n>>m;
	deque<int>q;
	map<int,int>mp;
	for(int i=1;i<=n;i++) cin>>w[i];
	for(int i=1;i<=m;i++){
		int x;
		cin>>x;
		b[i]=x;
		if(!mp[x]){
			q.push_back(x);
			mp[x]=1;
		}
	}
	int ans=0;
	for(int i=1;i<=m;i++){
		int sum=0;
		int t;
		for(int j=0;j<(int)q.size();j++){
			if(q[j]==b[i]){
				ans+=sum;
				t=j;
				break;
			}
			sum+=w[q[j]];
		}
		q.erase(q.begin()+t);
		q.push_front(b[i]);
	}
	cout<<ans<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

49.D. Decorate Apple Tree

n个节点的树
1为根节点
在每个叶子节点上放置一个灯泡,颜色可以不同
快乐节点:该节点的子树的所有灯泡颜色不同
问快乐节点数大于等于1所需的最少颜色,大于等于2,3,…n

参考codeforces1056D_ Decorate Apple Tree_牛客博客 (nowcoder.net)

反过来想,先考虑n的情况,要n个happy节点,那就是所有叶子都染不同颜色,然后考虑n-1的情况,就去掉一个根节点,那么就变成两棵独立的子树,那只要保证那个叶子节点多的那个子树的叶子染不同的颜色即可,另外的兄弟子树的叶子不管染什么色肯定不会超过这一棵的颜色数,所以其实就是第二大的叶子数的子树,所以答案就是dfs一遍预处理出每个节点对应的子树的叶子节点数,排序输出即可

只要求出每个节点子树的叶子节点个数即可,存入数组,从小到大排序,第i个就表示i个快乐节点所需的最少颜色数

trick:

1.节点的子树:包含该节点以及往下的所有节点

2.图论一定要特判n为1和m为1

3.求编号为id的子树的叶子节点数:

map<int,vector<int>>e;
void dfs(int u,int fa){
	if(e[u].size()==1&&u!=1){
		num[u]=1;
		return;
	}
	for(auto v:e[u]){
		if(v==fa) continue;
		dfs(v,u);
		num[u]+=num[v];
	}
}

4.对于从 1到 n 的每个 k ,要使快乐结点的数量大于或等于 k ,至少需要多少种不同颜色的灯泡?

像这种问法 ,1到n的情况都要回答,它们之间往往是有联系的

可以先考虑1的情况,然后一个一个往后推

也可以先考虑n的情况,然后一个一个往前推

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int num[N];
int n;
map<int,vector<int>>e;
void dfs(int u,int fa){
	if(e[u].size()==1&&u!=1){
		num[u]=1;
		return;
	}
	for(auto v:e[u]){
		if(v==fa) continue;
		dfs(v,u);
		num[u]+=num[v];
	}
}
void solve() {
	cin>>n;
	if(n==1){
		cout<<1<<endl;
		return;
	}
	for(int i=2;i<=n;i++){
		int x;
		cin>>x;
		e[i].push_back(x);
		e[x].push_back(i);
	}
	dfs(1,-1);
	sort(num+1,num+1+n);
	for(int i=1;i<=n;i++) cout<<num[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.F. Alex’s whims

n个顶点的树
操作:找两个相连的点u,v,然后删去它们之间的边,将u和x相连(u和x本来不相连)
在q天里
如果第i天没有两个叶子节点距离正好为di,是不允许的,因此需要进行以上操作最多一次,如果不需要执行操作,那么输出-1
注意,只要有两片距离为di的叶子就行 ,注意,只要有就行

一开始自己先构造一棵树,直接构造1到n的一条链
然后每次移动节点n,将n和节点d相连

trick:

构造题都是往简单特殊的方向想,主打一个妙字,不要想复杂

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/472870.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

7.【Linux】进程间通信2(共享内存||消息队列)

共享内存 介绍 1.共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间&#xff0c;这些进程间数据传递不再涉及到内核&#xff0c;换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。 2.当共享内存创建出来后&#xff0c;通过系统调用挂接到…

软件工程-第9章 软件工程项目管理概述

9.1 软件工程管理活动 9.2 软件规模、成本和进度估算 9.3 能力成熟度模型CMM 9.4 ISO 9000系列标准简介 9.5 CMM与ISO 9000系列标准的比较 9.6 本章小结

安装MySQL5.7.19 + 解决数据库乱码

文章目录 1.删除mysql服务 sc delete mysql2.解压到D:\mysql5.7下3.配置管理员环境变量4.D:\mysql5.7\mysql-5.7.19-winx64下创建my.ini1.创建文件2.文件内容 5.管理员打开cmd&#xff0c;切换到 D:\mysql5.7\mysql-5.7.19-winx64\bin6.输入 mysqld -install 安装mysql服务7.初…

python 中怎样使用任意关键词实参?

在 Python 中&#xff0c;可以使用任意数量的关键字实参和任意关键字实参&#xff0c;也被称为 kwargs。 这允许你在函数调用时传递任意数量的关键字参数。 你可以使用任意数量的关键字实参&#xff08;Keyword Arguments&#xff09;和任意关键字实参&#xff08;Arbitrary Ke…

LeetCode每日一题——最后一个单词的长度

最后一个单词的长度OJ链接&#xff1a;58. 最后一个单词的长度 - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 思路 &#xff1a; 统计字符串中最后一个单词的长度&#xff0c;那么我们可以定一一个指针&#xff0c;从后向前开始统计&#xff0c;当指针指向的元素…

C语言-----冒泡排序

今天&#xff0c;让我们来学习一下C语言中一个简单的排序算法------冒泡排序。 什么是冒泡排序呢&#xff1f; 冒泡排序是C语言中一个可以将一个数组的内容按照升序或者降序进行重新排列的算法。简单来说&#xff0c;是一种排序的思维。 冒泡排序的核心思想&#xff1a;让同…

2352.相等行列对

题目&#xff1a;给一个下标从0开始、大小为n x n的整数矩阵grid&#xff0c;返回满足Ri 行和 Cj 列相等的行列对&#xff08;Ri,Cj&#xff09;的数目。 如果行和列以相同的顺序包含相同的元素&#xff08;即相等的数组&#xff09;&#xff0c;则认为二者是相等的。 解题思路…

Vue3+.NET6前后端分离式管理后台实战(四)

1&#xff0c;Vue3.NET6前后端分离式管理后台实战(四)已经发布&#xff0c; 程序源码已打包&#xff0c;感兴趣的可以关注下载。 2&#xff0c;源码打包可以下载&#xff1a;

初始Java篇(JavaSE基础语法)(3)

个人主页&#xff08;找往期文章包括但不限于本期文章中不懂的知识点&#xff09;&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 目录 方法的使用 方法定义 实参和形参的关系 方法重载 方法签名 递归 方法的使用 方法就是一个代码片段. 类似于 C 语言中的 "函数"…

【前端】卡片渐变色阴影效果 旋转动画

【前端】卡片渐变色阴影效果 旋转动画 <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>Breathing…

Java集合Collection之LinkedList

LinkeList LinkedList&#xff08;双向链表&#xff09;是一种常见的线性数据结构&#xff0c;但是并不会按线性的顺序存储数据。它由一系列节点组成&#xff0c;每个节点包含数据部分和一个指向下一个节点的引用。相比于数组&#xff0c;链表具有动态大小、插入和删除效率高的…

golang 对接第三方接口 RSA 做签(加密) 验签(解密)

一、过程 1.调用第三方接口前&#xff0c;一般需要按规则将参数按key1value1&key2value2 阿斯克码排序,sign参数不参与加密 2.将排序并连接好的参数字符串通过我方的私钥证书&#xff08;.pem&#xff09;进行加密得到加密串&#xff0c;当然加密得到的是 []byte 字节流&…

2024 Python3.10 系统入门+进阶(二):Python编程环境搭建

目录 一、Windows安装Python1.1 下载并安装 Python1.2 测试安装是否成功 二、Linux系统安装Python(新手可以跳过)2.1 基于RockyLinux系统安装Python(编译安装)2.2 基于Ubuntu系统安装Python(编译安装) 三、如何运行Python程序&#xff1f;3.1 Python 交互式编程3.2 编写Python源…

使用STM32 再实现电动车防盗

项目需求 点击遥控器 A 按键&#xff0c;系统进入警戒模式&#xff0c;一旦检测到震动&#xff08;小偷偷车&#xff09;&#xff0c;则喇叭发出声响报警&#xff0c; 吓退小偷。 点击遥控器 B 按键&#xff0c;系统退出警戒模式&#xff0c;再怎么摇晃系统都不会报警&…

命名空间——初识c++

. 个人主页&#xff1a;晓风飞 专栏&#xff1a;数据结构|Linux|C语言 路漫漫其修远兮&#xff0c;吾将上下而求索 文章目录 经典的Hello Word 起航c关键字c语言的命名冲突问题域作用限定符::命名空间 namespace命名空间定义命名空间的使用1.加命名空间名称及作用域限定符2.使用…

IO扩展芯片应用及方案选型 (74HC595,74HC165,8255,CH351等)

IO扩展芯片应用及方案选型 (74HC595,74HC165,8255,CH351等) 参考书籍《振南技术干货集&#xff1a;单片机–基础进阶创业十年》作者&#xff1a;于振南 在我们进行单片机开发的时候, 经常会发现I/O 口不够用。 一方面是因为我们产品中往往都包括很多的功能, 又有显示, 又有存储…

PolarDN MISC(简单)大礼包 :详细思路过程

0和255 题目给了俩个文件&#xff0c;一个.txt,一个.py .txt文件中包含0和255 一个字节有八位&#xff0c;每一位只能储存1或0&#xff0c;计算机只懂二进制&#xff0c;所以就是2的八次方&#xff0c;又计算机规定从0开始计数&#xff0c;所以是0至255 考虑用编码转换工具将其…

【LeetCode 算法刷题笔记】

题一&#xff1a;1.0151. 反转字符串中的单词 1.1 题目大意 描述&#xff1a;给定一个字符串 s。 要求&#xff1a;反转字符串中所有单词的顺序。 说明&#xff1a; 单词&#xff1a;由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的单词分隔开。 输入字符串 s…

SQL语句之SELECT语句

一般格式 SELECT DISTINCT/ALL 目标列表达式 //要显示的属性列 FROM 表名/视图名 //查询的对象 WHERE 条件表达式 //查询条件 GROUP BY 列名 HAVING 条件表达式 //查询结果分组 ORDER BY 列名 次序; //最终查询结果排序 文章目录 一、基本查询 1、SELECT 目标列表达…

猜数字游戏有三变(Java篇)

本篇会加入个人的所谓‘鱼式疯言’ ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…