提示:本系列文章主要用于本人归纳整理,仅供个人学习和参考。因笔者水平有限,部分解法参考了其他博主,在此均会贴出原链接。若有原作者认为有侵权行为,请联系我删除。
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卡了不少时间,还是代码实现能力不足导致。