2019.02.28【HAOI2018】【BZOJ5302】【洛谷P4495】奇怪的背包(裴蜀定理)

BZOJ传送门

洛谷传送门


解析:

熟悉 e x g c d exgcd exgcd的人都知道这道题的结论。。。

每一个 v i v_i vi等价于 g c d ( v i , P ) gcd(v_i,P) gcd(vi,P)

最后要求的 w w w在模意义下实际上就是 g c d ( w , P ) gcd(w,P) gcd(w,P)

O ( P ) O(\sqrt P) O(P )时间处理出所有 P P P的约数就能愉快地水过了

当然还是讲一下怎么做。

以下的 v i v_i vi w w w均是取了 g c d gcd gcd之后的。

由裴蜀定理,最后选择的 v i v_i vi要能够凑出 w w w,需要满足 gcd ⁡ i = 1 t ( v i ) ∣ w \gcd\limits_{i=1}^t(v_i)\mid w i=1gcdt(vi)w

所以只需要预处理出最终 g c d gcd gcd等于每个约数的方案数就行了。

同时统计一下每个约数的因数前缀和

预处理复杂度大概是 O ( σ ( P ) 2 ) = O ( P 2 3 ) O(\sigma(P)^2)=O(P^{\frac{2}{3}}) O(σ(P)2)=O(P32)

然后就可以 O ( 1 ) O(1) O(1)回答每个询问了。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<20|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	inline int getint(){
		re char c;
		while(!isdigit(c=gc()));re int num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
}
using namespace IO;

struct Map{
	cs static int magic=189859;
	int key[magic],val[magic];
	Map(){memset(key,-1,sizeof key);};
	
	cs int &operator[](cs int &k)cs{
		int h=k%magic;
		while((~key[h])&&(key[h]^k))h=(h+1)%magic;
		return val[h];
	}
	
	int &operator[](cs int &k){
		int h=k%magic;
		while((~key[h])&&(key[h]^k))h=(h+1)%magic;
		if(key[h]^k)key[h]=k;
		return val[h];
	}
}id;

cs int mod=1e9+7;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(int a,int b){return a<b?a-b+mod:a-b;}
inline int mul(int a,int b){return (ll)a*b%mod;}

cs int N=1e6+6,M=1e5+5;
int n,q,P;
int v[N];
int factor[M],fcnt,cnt[M];
int f[M],now;
int ans[M];
int pow2[N],sum[N];

inline void sieve(){
	for(int re i=1;(ll)i*i<=P;++i)if(P%i==0)factor[id[i]=++fcnt]=i;
	for(int re i=fcnt;i;--i)if(P/factor[i]!=factor[i])factor[id[P/factor[i]]=++fcnt]=P/factor[i];
}

inline int gcd(int a,int b){
	if(!a||!b)return a|b;
	re int shift=__builtin_ctz(a|b);
	for(b>>=__builtin_ctz(b);a;a-=b)if((a>>=__builtin_ctz(a))<b)swap(a,b);
	return b<<shift;
}

inline void init(){
	for(int re i=1;i<=n;++i)++cnt[id[gcd(v[i],P)]];
	pow2[0]=1;
	for(int re i=1;i<=n;++i)pow2[i]=mul(2,pow2[i-1]);
	for(int re i=1;i<=n;++i)pow2[i]=dec(pow2[i],1);
	for(int re i=1;i<=fcnt;++i)if(cnt[i]){
		for(int re j=1;j<=fcnt;++j)if(f[j]){
			int nxt=gcd(factor[i],factor[j]);
			int pos=id[nxt];
			f[pos]=add(f[pos],mul(f[j],pow2[cnt[i]]));
		}
		f[i]=add(f[i],pow2[cnt[i]]);
	}
	for(int re i=fcnt;i;--i)
	for(int re j=i-1;j;--j)if(factor[i]%factor[j]==0)f[i]=add(f[i],f[j]);
}

signed main(){
	n=getint();q=getint();P=getint();
	sieve();
	for(int re i=1;i<=n;++i)v[i]=getint();
	init();
	while(q--)cout<<f[id[gcd(getint(),P)]]<<"\n";
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值