提示:本系列文章主要用于本人归纳整理,仅供个人学习和参考。因笔者水平有限,部分解法参考了其他博主,在此均会贴出原链接。若有原作者认为有侵权行为,请联系我删除。

目录


A

传送门
题意:挑选1~n中m个不同的数,构造出所有子序列的和不为k的序列,要求m最大。
思路:构造+简单思维。因为k>=1且k<=n,显然比k大的数和不可能为k,而比k小的数只要取到k/2前面即可(k/2要向上取整)
code:

#include <bits/stdc++.h>
using namespace std;
int main(){
   
	int t;cin>>t;
	while (t--){
   
		int n,k;cin>>n>>k;
		int ans[n+1];
		int cnt = 0;
		for (int i=n;i>k;i--) ans[cnt++] = i;
		for (int i=k-1;i>k-1-k/2;i--) ans[cnt++] = i;
		cout<<cnt<<endl;
		for (int i=0;i<cnt;i++) cout<<ans[i]<<" ";
		cout<<endl;
	}
}

B

传送门
题意:自定义小时进制h,分钟进制m,给出当前时间,求最近的在镜子里合法的未来时间。
思路:模拟+简单规律。可以发现镜子里的合法时间仅包括0,1,2,5,8五个数字,故暴力枚举所有合法时间和其镜子里的映射时间,判断两者是否均符合进制,记录离当前时间最近的时间即可。
code:

#include <bits/stdc++.h>
#define INF 0x7f7f7f7f 
using namespace std;
char s[] = {
   '0','1','2','5','8'};
string s1[30];
int cnt;
int at(string s){
   //计算现实时间 
	int ans = 0;
	for (int i=0;i<s.size();i++) 
		ans = ans * 10 + s[i] - '0';
	return ans;
}
int bt(string s){
   //映射为镜子里的时间 
	int ans = 0;
	for (int i=s.size()-1;i>=0;i--) 
		if (s[i]=='2') ans = ans * 10 + '5' - '0';
		else if (s[i]=='5') ans = ans * 10 + '2' - '0';
		//注意2和5需要转换 
		else ans = ans * 10 + s[i] - '0';
	return ans;
}
int main(){
   
	for (int i=0;i<5;i++){
   
		for (int j=0;j<5;j++){
   
			string ss;ss += s[i];
			ss += s[j];
			s1[cnt++] = ss,ss.clear();
		}
	}//预处理所有合法时间 
	int t;cin>>t;
	while (t--){
   
		int h,m;cin>>h>>m;
		int a,b;
		scanf("%d:%d",&a,&b);
		int mini = INF;
		string ans_h,ans_m;
		for (int i=0;i<cnt;i++){
   //暴力枚举所有合法时间 
			for (int j=0;j<cnt;j++){
   
				int hi = at(s1[i]),mi = at(s1[j]);
				int hh = bt(s1[j]),mm = bt(s1[i]);
				//hi,mi存现实时间
				//hh,mm存镜子时间 注意小时和分钟互换 
				if ((hi>=h)||(mi>=m)||(mm>=m)||(hh>=h)) continue;
				//判断是否符合要求的进制 
				int ti = (hi * m + mi) - (a * m + b);
				//ti计算同一天的时间差 
				if (ti<mini&&ti>=0){
   
					mini = ti;
					ans_h = s1[i],ans_m = s1[j];
				}
				int si = hi * m + mi + h * m - (a * m + b);
				//si计算不同天的时间差 
				if (abs(si)<mini){
   
					mini = abs(si);
					ans_h = s1[i],ans_m= s1[j];
				}
			}
		}
		cout<<ans_h<<":"<<ans_m<<endl;
	}
}

C

传送门
题意:给长度为n的字符串,在原字符串的基础上对任意字符进行替换,使每个字母出现的次数都能被k整除。要求构造出的新字符串是比原字符串的字典序大的最小字符串。
思路:贪心+模拟。为构造出比原字符串大的最小字符串,显然是从后往前对字符进行向上替换。具体可以看代码注释。
code:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int mp[27];
int n,k;
int get(int x){
   
	return (k - x % k) % k;
}//计算变成k的倍数还需要多少数 
int main(){
   
	int t;cin>>t;
	while (t--){
   
		cin>>n>>k;
		string s;cin>>s;
		int sum = 0;
		for (int i=0;i<26;i++) mp[i] = 0;
		for (auto it:s) mp[it-'a']++;
		//mp计算各个字母的出现次数 
		for (int i=0;i<26;i++) {
   
			if (mp[i]){
   
				sum += get(mp[i]);	
				//sum记录总共要变多少数 
			}
		}
		if (n%k){
   //如果字符长度本身不能被k整除,直接输出-1 
			cout<<-1<<endl;
			continue;
		}
		if (!sum){
   //如果需要变的次数为0,直接输出原字符串 
			cout<<s<<endl;
			continue;
		}
		for (int i=n-1;i>=0;i--){
   //倒序遍历 
			sum -= get(mp[s[i]-'a']);
			mp[s[i]-'a']--;//将s[i]"删去” 
			sum += get(mp[s[i]-'a']);
			for (int j=s[i]+1-'a';j<26;j++){
   //挑选替换s[i]的数 
				int need = sum;
				need -= get(mp[j]);
				mp[j]++;
				need += get(mp[j]);
				if (i+need<n){
   //如果当前下标+需要增加的数小于n,即合法 
					for (int pre=0;pre<=i-1;pre++) cout<<s[pre];
					//输出前面未改变的数 
					cout<<char(j+'a');
					//输出替换的数 
					for (int last=i+need+1;last<n;last++) cout<<'a';
					//将空位用'a'补上 
					for (int i=0;i<26;i++){
   
						int num = get(mp[i]);
						while (num--) cout<<char(i+'a');
					}//输出需要增加的数 
					cout<<endl;
					goto sign;
				}
				mp[j]--;//如果不合法 回溯 
			}
		}
		sign:
			continue;
	}
}

D

传送门
题意:给n个数,进行q次操作,输出每次操作后序列的最大公约数
思路:数学。可以发现,只要存在质因子在每个数中的个数相同,使用哈希表维护,将所有这样的质因子乘起来即为答案。(笔者代码用质数筛略繁琐了,用试除法更好)
code:

#include <bits/stdc++.h>
#define int long long //注意要开long long
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using namespace std;
const int mod = 1e9 + 7;
const int N = 2e5 + 7;
int n,q;
int a[N],prime[N];
bool vis[N];
int ans = 1,cnt;
unordered_map<int,unordered_map<int,int>>mp,cn;
//mp[i][prime[j]]存第i个数出现质数prime[j]的次数
//cn[prime[j][mp[i][prime[j]]存prime[j]在整个序列出现mp[i][prime[j]]次的次数 
void get_prime(){
   //欧拉筛 
	for (int i=2;i<=2e5;i++){
   
		if (!vis[i]) prime[cnt++] = i;
		for (int j=0;j<cnt&&prime[j]*i<=2e5;j++){
   
			vis[i*prime[j]] = 1;
			if (i%prime[j]==0) break;
		}
	}
}
void divide(int i,int num){
   //i为下标,num为乘上的值 
	for (int j=0;j<cnt&&num>=prime[j]*prime[j];j++){
   
		if (num%prime[j]==0){
   //枚举num的质因子 
			int sum = 0;
			while (num%prime[j]==0){
   
				mp[i][prime[j]]++;//次数加一 
				cn[prime[j]][mp[i][prime[j]]]++;//出现次数的次数加一 
				if (cn[prime[j]][mp[i][prime[j]]]>=n)  ans = (ans * prime[j]) % mod;
				//如果出现prime[j]次数的次数大于等于n,那么所有数都有公因子prime[j]
				//ans * prime[j] 同时取模 
				num /= prime[j];
			}
			//cout<<"sum = "<<sum<<endl;
		}
	}
	if (num>1) {
   //若剩下大质数 同理操作 
		mp[i][num]++;
		cn[num][mp[i][num]]++;
		if (cn[num][mp[i][num]]==n)  ans = (ans * num) % mod; 
	}
}
signed main(){
   
	IOS
	cin>>n>>q;
	get_prime();
	for (int i=1;i<=n;i++){
   
		cin>>a[i];
		divide(i,a[i]);
	}
	while (q--){
   
		int i,x;cin>>i>>x;
		divide(i,x);
		cout<<ans%mod<<endl;
	}
}

E

传送门
题意:给出长度为n的两个二进制,第一个为l指针,第二个为r指针,l有前导零而r无。选取在l~r的区间,使之异或最大,输出这个最大值。
思路:思维+找规律
1.若l==r,直接输出任意一个
2.若l和r不同位(即s1[0]=0 s2[0]=1),必定能构造出区间使所有位为1
3.若l和r同位,并且l和r只差1,那么最大值就是r本身
4.若l和r同位,发现当r为奇数时,最大值为r本身;而当r为偶数时,最大值为r+1.
(异或题判断奇偶性是常规套路)

#include<bits/stdc++.h>
using namespace std;
int n;
bool judge(string s1,string s2){
   //该函数判断s1+1是否等于s2 
	int flag = 0;
	for (int i=n-1;i>=0;i--){
   
		if(s1[i]=='0'&&!flag){
   
			s1[i] += 1;
			break;
		} 
		else if (s1[i]=='0'&&flag){
   
			flag = 0;
			s1[i] += 1;
			break;
		}
		else if (s1[i]=='1'&&!flag){
   
			s1[i] = '0';
			flag = 1;
		}
		else if (s1[i]=='1'&&flag){
   
			s1[i] = '0';
		}
	}
	return s1==s2;
}
int main(){
   
	cin>>n;
	string s1,s2;cin>>s1>>s2;
	if (s1==s2) cout<<s1<<endl;//相等直接输出 
	else if (s1[0]=='0'&&s2[0]=='1'){
   
		for (int i=0;i<n;i++) cout<<'1';
	} //不同位直接全输出1 
	else{
   //讨论同位数的情况 
		if (s2[n-1]=='1'||judge(s1,s2)) cout<<s2<<endl;
		//s2为奇数或s1+1==s2 输出s2 
		else if (s2[n-1]=='0'){
   
		//s2为偶数输出s2 + 1 
			s2[n-1] += 1;
			cout<<s2<<endl;
		}
	}
} 

F

传送门
题意:问矩阵最多能被分为几个相同矩阵。
思路:数学+思维。还是求质因数,只是这次要储存的是最小质因子,询问方式进行简单的分类讨论即可。参考其他博主
注意交互题给出的样例和本机测试样例可能不一致。

#include <bits/stdc++.h>
using namespace std;
const int N = 1100;
int prime[N];
int n,m;
bool askn(int p,int f){
   //p分了几段,f每段的长度 
	if (p==2){
   
		int ans;
		printf("? %d %d %d %d %d %d\n",f,m,1,1,f+1,1);
		fflush(stdout);
		scanf("%d",&ans);
		return ans;
	}
	else {
   //如果分了奇数段 
		int ans1,ans2;
		printf("? %d %d %d %d %d %d\n",f*(p/2),m,1,1,f*(p/2)+1,1);
		fflush(stdout);
		scanf("%d",&ans1);
		printf("? %d %d %d %d %d %d\n",f*(p/2),m,1,1,f*(p/2+1)+1,1);
		fflush(stdout);
		scanf("%d",&ans2);
		return ans1&ans2;
	}
}
bool askm(int p,int f){
   //p分了几段,f每段的长度 
	if (p==2){
   
		int ans;
		printf("? %d %d %d %d %d %d\n",n,f,1,1,1,f+1);
		fflush(stdout);
		scanf("%d",&ans);
		return ans;
	}
	else {
   //如果分了奇数段 
		int ans1,ans2;
		printf("? %d %d %d %d %d %d\n",n,f*(p/2),1,1,1,f*(p/2)+1);
		fflush(stdout);
		scanf("%d",&ans1);
		printf("? %d %d %d %d %d %d\n",n,f*(p/2),1,1,1,f*(p/2+1)+1);
		fflush(stdout);
		scanf("%d",&ans2);
		return ans1&ans2;
	}
}
int main(){
   
	cin>>n>>m;
	int nn = n,mm = m;
	for (int i = 2; i <= 1000; ++i) {
   
        if (!prime[i]) {
   
            prime[i] = i;
            for (int j = i * i; j <= 1000; j += i) {
   
                if (!prime[j]) prime[j] = i;
            }
        }
    }//prime[i]存i的最小因子 
	for (int i=n;i>1;i/=prime[i]){
   
		if (askn(prime[i],n/prime[i])){
   
			n /= prime[i];
		}
	} 
	for (int i=m;i>1;i/=prime[i]){
   
		if (askm(prime[i],m/prime[i])){
   
			m /= prime[i];
		}
	}
	n = nn / n;
	m = mm / m;
	int cnt = 0,snt = 0;
	for (int i=1;i<=n;i++) if (n%i==0) cnt++;
	for (int i=1;i<=m;i++) if (m%i==0) snt++;
	printf("! %d\n",cnt*snt);
	fflush(stdout);
}

总结

最后三题并没有想象中的难。反而是BC卡了不少时间,还是代码实现能力不足导致。