P6104 [EER2]相同的数字:贪心+线性筛+模拟+后缀和

题目来源: P6104 [EER2]相同的数字.
题目大意:对一串固定的数字,可以每次将数字+1花费c1,可以将数字变为比他大的最小的质数,花费c2,最终要将所有数字变得一样。问最小花费是多少?
这个题目思路自己想的,过了样例后0分,又参考题解修改了写小细节。
因为题目给的变化是只能是变大,所以将数字排序,每个数字变成其中最大的数字a[n],或者比a[n]大的最小质数。
怎么变化呢?要么一步一步跳,要么质数的间距跳,设质数的间距t,打了个1e7的表看了下,最大间距只有154,可以用桶来计数.间距越大,那么用c2跳越合算,如果c1*t>c2;那么就用质数的方法跳;可以得到结论:t>c2/c1,那么用的c2方法,否则用c1方法。将所有质数跳的次数和跳的距离维护后缀和,然后根据c2/c1的计算找到对应的位置,o(1)即可计算。
所以问题就变为如何统计这些数在质数上的跳跃次数和+1跳跃次数。这里的细节比较多。
因为n,q都比较大,所以应该预处理n,然后再线性处理q。


#include<cstdio>
#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e7 + 160;
const int mod = (1<<17);
 
bool isnp[MAXN];
int p[MAXN],pcnt=0;
ll sump[MAXN];//sump[]存储到当前数为值出现的质数的个数,其下一个质数就是p[sum[]+1]。 
ll n,q,T,a[MAXN],c[2][200],maxa,maxp,ans,lans=0,sumstep[2],behc[2][200],behs[2][200]; 
 
void prim(int N) {
	isnp[0]=isnp[1]=1;
	for(int i=2; i<N; ++i) {
		if(!isnp[i]) {
			p[++pcnt]=i;			
		}
		sump[i]=pcnt;
		for(int j=1; j<=pcnt; ++j) {
			if((ll)i*p[j]>=N) break;
			isnp[i*p[j]]=1;
			if(i%p[j]==0) 	break;
		}
	}	
}
//求这些数到终点x的的值终点有两种可能,到n;如果n不是质数,到n对应的下一个质数 
void count(int flag,int x){//计算出:1.需要跳多少个1步sumstep;2桶计数c[]:跳到对应的质数间距计数 
	sumstep[flag]=0;
	if(flag==0)//这些数要跳到一个合数,只能+1来跳。 
		sumstep[flag]+=(n-1)*(a[n]-p[sump[a[n]]]),a[n]=p[sump[a[n]]];
	else if(isnp[a[n]]){//可以通过+1跳到这个质数,也可以通过下一个质数跳到。
		sumstep[flag]+=p[sump[a[n]]+1]-a[n];
		c[flag][sumstep[flag]]++;
		a[n]=p[sump[a[n]]+1];
	}
	int j=sump[a[n]];
	for(int i=n;i>1;i--){
		sumstep[flag]+=(a[n]-a[i-1]); //+1来挑 
		while(p[j]>a[i-1]&&p[j-1]>=a[i-1]&&j){
			c[flag][p[j]-p[j-1]]+=(i-1);//从质数跳到质数 
			j--;
		}
		if(isnp[a[i-1]])c[flag][p[j]-a[i-1]]++; //第一步可能是合数跳到质数	
	}
	//因为质数跳的距离越大用c2越合算,维护c[t]的后缀和,和所代替步数t*c[t]的后缀和 
	for(int i=161;i>=1;i--){
	
		behc[flag][i]=behc[flag][i+1]+c[flag][i];
		behs[flag][i]=behs[flag][i+1]+i*c[flag][i];
	}
	a[n]=x;	
} 
ll work(ll c1,ll c2){

	c1=c1^(T*lans),c2=c2^(T*lans); 
	ll t=ceil(1.0*c2/c1);
	if(t>160)t=160;
	ll sum0=(sumstep[0]-behs[0][t])*c1+behc[0][t]*c2;	
	ll sum1=(sumstep[1]-behs[1][t])*c1+behc[1][t]*c2;
	return min(sum0,sum1);	
	
} 
int main(void) {
//	freopen("in.txt","r",stdin);
	ll c1,c2;
	scanf("%lld%lld%lld",&n,&q,&T);
	for(int i=1;i<=n;i++) 
		scanf("%lld",&a[i]);
	sort(a+1,a+1+n);
	prim(a[n]+158) ;
	count(0,a[n]);
	count(1,a[n]);
	for(int i=1;i<=q;i++){
		scanf("%lld%lld",&c1,&c2);
		ans=work(c1,c2);
		lans=ans%mod;
		printf("%lld\n",ans);
	}	
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章