CF624D - Array GCD

文章大意:

给定一个数列,现在有两种合法操作:

1)选择一段连续区间,把这段区间的数移除,代价为a*区间元素个数,不可移动整个数列。

2)选择某些元素,把这些元素分别加一或减一,元素之间无影响,可以不连续。代价为b*操作元素个数。

现在要求这两种操作,每种操作最多做一次,可以不做,使得剩下的数列中,所有数的最大公约数大于1。求所需的最小代价。


通过YY可以发现,既然不让移除全部区间,就是说,a1或者an至少要有一个留在原来数列,那么可以求a1,a1+1,a1-1,an,an+1,an-1,这六个数的质因数,最后的答案的约数肯定在这六个数的质因数当中。那么久简单了,先分解这六个数,求出它们的质因数(ps:注意本身是大素数的情况),然后枚举质因数,对于每个质因数,求出其最小代价,最后求最小值。

求最小值可以DP也可以尺扫,我用的是尺扫的方法,枚举移除的左右端点L和R,如果当前区间的修改代价之和大于区间移除代价,那么就移除,R++,否则,不移除,L=R+1,R=L。这个复杂度是O(n)的,有兴趣的同学可以自己证明一下这个的复杂度。注意不可以整个区间都移除,需要特判。

<代码很丑,勿喷>

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<map>
using namespace std;
int shu[1100000];
int sushu[110000];
long long dai[1100000];
long long ans;
int n;
long long a[1100000];
long long aa,bb;
int aaa,bbb;
map<long long,int> w1,wn;
int main(){
	for(int i=2;i<=1000000;i++){
		if(!shu[i]){
			for(int j=i*2;j<=1000000;j+=i)shu[j]=1;
		}
	}
	for(int i=2;i<=1000000;i++){
		if(!shu[i]){
			sushu[0]++;
			sushu[sushu[0]]=i;
		}
	}
	scanf("%d%d%d",&n,&aaa,&bbb);
	aa=aaa;
	bb=bbb;
	for(int i=1;i<=n;i++){
		int x;
		scanf("%d",&x);
		a[i]=x;
	}
	for(int i=1;i<=sushu[0];i++){
		if(a[1]%sushu[i]==0)w1[sushu[i]]=1;
		if((a[1]-1)%sushu[i]==0)w1[sushu[i]]=1;
		if((a[1]+1)%sushu[i]==0)w1[sushu[i]]=1;
		w1[a[1]]=1;
		w1[a[1]+1]=1;
		w1[a[1]-1]=1;
//		if(w1[sushu[i]])printf("sajfdoiajfo 1 %d\n",sushu[i]);
		if(a[n]%sushu[i]==0)wn[sushu[i]]=1;
		if((a[n]-1)%sushu[i]==0)wn[sushu[i]]=1;
		if((a[n]+1)%sushu[i]==0)wn[sushu[i]]=1;
		wn[a[n]]=1;
		wn[a[n]-1]=1;
		wn[a[n]+1]=1;
//		if(wn[sushu[i]])printf("sajfdoiajfo n %d\n",sushu[i]);
	}
	sushu[0]++;
	sushu[sushu[0]]=a[1];
	sushu[0]++;
	sushu[sushu[0]]=a[1]+1;
	sushu[0]++;
	sushu[sushu[0]]=a[n];
	sushu[0]++;
	sushu[sushu[0]]=a[n]+1;
	sushu[0]++;
	sushu[sushu[0]]=a[1]-1;
	sushu[0]++;
	sushu[sushu[0]]=a[n]-1;
	ans=-1;
	for(int i=1;i<=sushu[0];i++){
		if(sushu[i]==1)continue;
//		if(i==10)while(1);
//		printf("ii=%d sushu=%d\n",i,sushu[i]);
		if((!w1[sushu[i]])&&(!wn[sushu[i]])){
		}else{
			long long q=sushu[i];
			for(int j=1;j<=n;j++){
				if(a[j]%q==0){
					dai[j]=0;
				}else{
					if(((a[j]+1)%q==0)||((a[j]-1)%q==0)){
						dai[j]=bb;
					}else{
						dai[j]=aa*(long long)100;
					}
				}
					
			}
			
			int ji=0;
			long long temp=0;
			long long anss;
			for(int j=2;j<=n;j++)dai[j]+=dai[j-1];
//			for(int j=1;j<=n;j++){
//				printf("dai[%d]=",j);
//				cout<<dai[j]<<endl;
//			}
			anss=dai[n];
			for(int j=1;j<=n;j++){
				temp+=dai[j]-dai[j-1];
				ji++;
				if(temp>=(long long)ji*aa){
					anss=min(anss,dai[j-ji]+dai[n]-dai[j]+(long long)ji*aa);
				}else{
					temp=0;
					ji=0;
				}
			}
			if(ans==-1)ans=anss;
			ans=min(ans,anss);
//			printf("ans[%d]=",i);
//			cout<<ans<<endl;
		}
		
	}
	cout<<ans<<endl;
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值