A题
题意:
给定一个数n,求出两个非质数a和b,满足a−b=na-b=na−b=n,输出a和b。
1≤n≤107\le n \le 10^7≤n≤107,2≤a,b≤1092\le a,b \le 10^92≤a,b≤109
原理:
判断一个数是不是质数,只需判断n是否会被[1,n\sqrt{n}n]中的正整数整除即可
算法:
从222到10910^9109一直枚举b即可,生成b的同时a=b+n,这样满足a-b=n
复杂度不超过O(n)O(n)O(n),1e9
代码:
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#define ll long long
using namespace std;
const int maxm=1e9+10;
bool is_prime(int n){
if(n==2) return true;//2是质数
else if(n%2==0) return false;//其它偶数都不是质数
for(int i=3;i<=sqrt(n);i+=2){//n肯定是除了2以外的奇数,所以每次+=2
if(n%i==0) return false;
}
return true;
}
int main(){
int n;
cin>>n;
int a=2,b=2+n;
while(1){
if(!is_prime(a)&&!is_prime(b)) break;//不断枚举
else a++,b++;
}
cout<<b<<" "<<a<<endl;
}
官方的题解有更巧妙的处理:直接输出9n,8n
因为8和9是最小的两个相邻的合数(差为1),所以这样输出一定符合题意
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#define ll long long
using namespace std;
int main(){
int n;
cin>>n;
cout<<9*n<<" "<<8*n<<endl;
}
B题
题意:
给n个数,称为数组a,每个数都不超过m,可以同时给这n个数加上x并且模m,使最终生成的数列的任意一个序列(permutation)和另一个也有n个数的数组b相同,问最小的x是多少,输出x
1≤n≤2000,1≤m≤1091\le n \le 2000,1\le m \le 10^91≤n≤2000,1≤m≤109
原理:
说是序列,其实我们并不关心到底是怎么排列的,关心的只是每个数的个数,因为加上x的时候所有数都要加,比如2+1,所有的2都会变成3,所以只要判断个数就行了,因此我们只需要枚举x,让数组(a+x)%m=b即可
但是由于m最大有1e9,依次枚举判断一定超时。所以我们可以用数组a的一个元素分别对应数组b的每一个元素生成x,取最小的x,这样生成x的最大复杂度就是2000*2000=4e6,对于生成的每个x进行判断其是否能使数组a==数组b就行了
算法:
依次用数组a的第一个数对应数组b的每个数就行了,因为b中肯定有数是对应a的第一个数,相应的也会生成x
用这个x去判断是否能让(a+x)%m=b
如果可以的话,用x更新ans,取最小
存在两种情况:
- a≤\le≤b,这个时候x=b-a
- a>b,这个时候x=b-a+m,因为x肯定是小于m的
复杂度O(n2)O(n^2)O(n2),但其实下面的代码复杂度O(n3)O(n^3)O(n3)…因为我写了循环,其实是可以优化的
代码:
#include <set>
#include <map>
#include <cstdio>
#include <iostream>
#include <unordered_map>
#define ll long long
using namespace std;
const int maxm=2e3+10;
int n,m;
int a[maxm],b[maxm];
unordered_map<int,int> numa,numb,tmp;
set<int> sta,stb;
bool able(int x){//判断整体给a+x能不能%m等于b
for(set<int>::iterator it=sta.begin();it!=sta.end();it++){
if(numa[*it]!=numb[(*it+x)%m]) return false;//数量不同就GG
}
return true;//a==b
}
int main(){
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++) {
scanf("%d",&a[i]);
numa[a[i]]++;//记录每个数出现的次数
if(!sta.count(a[i])) sta.insert(a[i]);//相当于离散化,记录这n个数
}
for(int i=0;i<n;i++){
scanf("%d",&b[i]);
numb[b[i]]++;
if(!stb.count(b[i])) stb.insert(b[i]);//同数组a的处理
}
int ans=1e9+10;
for(set<int>::iterator it=sta.begin();it!=sta.end();it++){//遍历数组a
for(set<int>::iterator it1=stb.begin();it1!=stb.end();it1++){//遍历数组b
if(numa[*it]!=numb[*it1]) continue;//两个数对应的个数不一样,一定不能生成
else {
int a=*it,b=*it1;
if(a<=b) {
if(able(b-a)) ans=min(ans,b-a);//a+x=b
}else {
if(able(b-a+m)) ans=min(ans,b-a+m);//a+x-m=b
}
}
}
}
cout<<ans<<endl;
}
C题
题意:
给一个长度为n的数b,求最小的大于等于这个数的数a,并且使a是一个长度为k的循环节形成的数
例如,13131,是由一个长度为2的循环节13形成的(当然也可以是长度为4的循环节形成的)
2≤n≤2e52\le n \le 2e52≤n≤2e5,1≤k<n1\le k \lt n1≤k<n
原理:
题意叙述清楚了,找最小的循环节就行了…
要注意的是:我们找的循环节一定是大于等于b的前k位的,所以下面judge函数是那样写的,但是它不具有普适性(如果循环节一上来就小于a的话,显然是不成立的,但是函数里却没判断)
算法:
取数b的前k位做循环节,称为x
- 如果生成的数大于a,那就是这个数了
- 如果生成的数小于a,那么让x增加呀~
可以证明a的位数是一定等于b的,因为这样才能让a最小。x自加不可能有进位的~
x最大是999⋯\cdots⋯,没有任何一个b可以大于相同位数的这个循环节组成的数
复杂度O(k)O(k)O(k)
代码:
#include <cstdio>
#include <iostream>
#define ll long long
using namespace std;
const int maxm=2e5+10;
char a[maxm],b[maxm];
char tmp[maxm];
int n,k;
bool judge(){
bool equ=0;
for(int i=0;i<n;i++){
if(tmp[i%k]>a[i]) return true;//如果循环节大于a了
else if(tmp[i%k]==a[i]) equ=1;//等于a的话
else if(equ&&tmp[i%k]<a[i]) return false;//前面都相等并且现在小于了
}
return true;
}
void add(char *s){
if(s[k-1]<'9') s[k-1]++;//小于9直接加
else{//不然得处理进位
int i=k-1;
while(i>=0){
if(s[i]=='9') {//遇见9进位
s[i]='0';
i--;
}else {//直接加
s[i]++;
break;
}
}
}
}
int main(){
scanf("%d%d",&n,&k);
scanf("%s",a);
for(int i=0;i<k;i++) tmp[i]=a[i];
while(1){
if(judge()) break;//如果可以了,tmp就是最小的循环节
add(tmp);//不行就自加
break;
}
cout<<n<<endl;
for(int i=0;i<n;i++){
printf("%c",tmp[i%k]);
}
printf("\n");
}
其实这里如果不符合的话只需要让循环节自加1就行了,没必要在while循环里再写再判断,因为循环节自加1之后形成的数必定比原数要大!
D题
题意:
给n个降序的数aia_iai。代表每一个横坐标为iii的地方有高度为aia_iai的块,现在我们有很多个长度为2的块,问最多能在给的n个块指定的空间内放多少个长度为n的块。
原理:
一个黑白块的问题,我们依次把空间涂成黑色-白色-黑色-⋯\cdots⋯,如果想要放置一个块,则一定要覆盖一个黑块和一个白块,所以输出黑白数量最小的就可以了
涂完颜色之后能扩展就扩展,不能扩展就说明必然不符合条件了
算法:
这里抄了大佬qscqesze的一段代码,用来计算黑白块的,非常巧妙
for(int i=0;i<n;i++){
cin>>a[i];
b[i%2]+=a[i]/2+a[i]%2;//如果i为偶数,则统计的是黑块,如果i为奇数,则统计的是白块
b[(i+1)%2]+=a[i]/2;//如果i为偶数,则统计的是白块,如果i为奇数,则统计的是黑块
}
cout<<min(b[0],b[1])<<endl;
i是偶数的话黑块较多,为奇数的话白块较多
代码:
#include <cstdio>
#include <iostream>
#define ll long long
using namespace std;
const int maxm=3e5+10;
int n;
int a[maxm];
ll b[2];//不开long long见祖宗
int main(){
cin>>n;
for(int i=0;i<n;i++){
cin>>a[i];
b[i%2]+=a[i]/2+a[i]%2;
b[(i+1)%2]+=a[i]/2;
}
cout<<min(b[0],b[1])<<endl;
}