Codeforces Round #609(div2) [A-D]

A题

题意:

给定一个数n,求出两个非质数a和b,满足a−b=na-b=nab=n,输出a和b。

1≤n≤107\le n \le 10^7n1072≤a,b≤1092\le a,b \le 10^92a,b109

原理:

判断一个数是不是质数,只需判断n是否会被[1,n\sqrt{n}n]中的正整数整除即可

算法:

22210910^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^91n20001m109

原理:

说是序列,其实我们并不关心到底是怎么排列的,关心的只是每个数的个数,因为加上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≤\leb,这个时候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 2e52n2e51≤k<n1\le k \lt n1k<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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值