P4495 [HAOI2018]奇怪的背包

题意

考虑\(n=1\)的情况,我们由裴蜀定理可知,\(k*a_1\%P\)能表示\(\gcd(a_1,P)\)的所有倍数。

扩展到多个数也是同理(不会证):
\((k_1a_1+k_2a_2+...+k_na_n)\%P\)能表示出\(\gcd(a_1,a_2,...,a_n)\)的所有倍数。

于是令\(v_i=\gcd(v_i,P)\)后问题是等价的。

之后处理出\(P\)的所有约数,假设数量为\(m\),存在\(a\)数组中。

\(f_{i,j}\)表示考虑\(P\)的前\(i\)个约数,取出的数的\(\gcd\)\(a_j\)的方案数,\(cnt_i\)表示\(a_i\)这个约数在\(v\)(取过\(\gcd\)的)中出现的次数。
\(f_{i,k}+=f_{i-1,j}*(2^{cnt_i}-1)\)
\(f_{i,i}+=2^{cnt_i}-1\)

之后对于每个\(w_i\),我们枚举它的约数:
\(ans=\sum\limits_{a_i|w}f_{n,i}\)

但这样询问\(mq\)的,我们要进一步优化:
发现\(a_i\)也是\(P\)的约数,所以\(a_i|(\gcd(P,w))\),因此可以使\(w=\gcd(w,P)\)
我们预处理\(g_i=\sum\limits_{a_j|a_i}f_{n,j}\),这样就可以\(O(1)\)处理每次询问了。

code:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
const int maxm=5e4+10;
const int mod=1e9+7;
int n,m,P,Q;
int a[maxm],val[maxn],cnt[maxm],pw[maxn],g[maxm];
int f[2][maxm];
int main()
{
	scanf("%d%d%d",&n,&Q,&P);
	for(int i=1;i<=n;i++)scanf("%d",&val[i]),val[i]=__gcd(val[i],P);
	for(int i=1;i*i<=P;i++)
	{
		if(P%i)continue;
		a[++m]=i;
		if(P!=i*i)a[++m]=P/i;
	}
	sort(a+1,a+m+1);
	for(int i=1;i<=n;i++)cnt[lower_bound(a+1,a+m+1,val[i])-a]++;
	pw[1]=2;
	for(int i=2;i<=n;i++)pw[i]=1ll*pw[i-1]*2%mod;
	for(int i=1;i<=n;i++)pw[i]=(pw[i]-1+mod)%mod;
	for(int i=1;i<=m;i++)
	{
		int now=i&1;
		for(int j=1;j<=m;j++)f[now][j]=f[now^1][j];
		for(int j=1;j<i;j++)
		{
			int k=__gcd(a[i],a[j]);
			k=lower_bound(a+1,a+m+1,k)-a;
			f[now][k]=(f[now][k]+1ll*f[now^1][j]*pw[cnt[i]]%mod)%mod;
		}
		f[now][i]=(f[now][i]+pw[cnt[i]])%mod;
	}
	for(int i=1;i<=m;i++)
		for(int j=1;j<=i;j++)
			if(a[i]%a[j]==0)g[i]=(g[i]+f[m&1][j])%mod;
	while(Q--)
	{
		int k;scanf("%d",&k);
		k=__gcd(k,P);
		printf("%d\n",g[lower_bound(a+1,a+m+1,k)-a]);
	}
	return 0;
}
posted @ 2020-01-06 09:31  nofind  阅读(358)  评论(0编辑  收藏  举报