[bzoj5302][Haoi2018]奇怪的背包——数论+背包

###题目大意:
题面
###思路:
首先你发现一个集合里面的物品可以组合成的总体积必定是 g c d ( v i , p ) , i ∈ S gcd(v_i,p),i\in S gcd(vi,p),iS,于是为了防止重复计算,一个方案用它的gcd来表示。
于是对于每一个物品,可以直接使得 v i = g c d ( v i , p ) v_i=gcd(v_i,p) vi=gcd(vi,p),然后我们将p的所有的约数提取出来,做一个dp,dp[i][j]表示前i个物品组合使得gcd为p的第j个约数的方案数。
d p [ i ] [ j ] = d [ i − 1 ] [ j ] + d p [ i ] [ k ] × ( 2 c n t [ i ] − 1 )      ( g c d ( v a l [ i ] , f a c [ k ] ) = = f a c [ j ] ) dp[i][j]=d[i-1][j]+dp[i][k]\times(2^{cnt[i]}-1) \ \ \ \ (gcd(val[i],fac[k])==fac[j]) dp[i][j]=d[i1][j]+dp[i][k]×(2cnt[i]1)    (gcd(val[i],fac[k])==fac[j])
转移的时候没有必要对j枚举上一层的k,因为上一层的每一个k保证只会对应这一层的一个j(我在这里就没有想清楚)。
考虑如何计算答案,因为组成的gcd只会是p的约数,而答案又必须是 w i w_i wi的约数,所以必定是 g c d ( p , w i ) gcd(p,w_i) gcd(p,wi)的约数,所以我们只需要对p的每一个约数 d j d_j dj处理 g c d ( p , w i ) gcd(p,w_i) gcd(p,wi) d j d_j dj的答案就好了。

#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
typedef long long ll;

using namespace std;

void File(){
	freopen("loj2523.in","r",stdin);
	freopen("loj2523.out","w",stdout);
}

template<typename T>void read(T &_){
	T __=0,mul=1; char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')mul=-1;
		ch=getchar();
	}
	while(isdigit(ch))__=(__<<1)+(__<<3)+(ch^'0'),ch=getchar();
	_=__*mul;
}

template<typename T>void write(T x,char ch){
	if(!x){putchar('0');putchar(ch);return;}
	ll len=1,y=10;
	while(y<=x)y=(y<<1)+(y<<3),++len;
	while(len--){
		y/=10;
		putchar(x/y+48);
		x%=y;
	}
	putchar(ch);
}

const int maxn=1e6+10;
const int maxd=5e3+10;
const ll mod=1e9+7;
int n,q,p,num[maxn],d[maxn],tot,val[maxn],cnt[maxn],w[maxn];
ll dp[maxd][maxd],ans[maxn],Pow[maxn];
map<int,int>mp;

void init(){
	read(n); read(q); read(p);
	REP(i,1,n)read(val[i]),val[i]=__gcd(val[i],p);
	REP(i,1,q)read(w[i]),w[i]=__gcd(w[i],p);
	REP(i,1,n)++mp[val[i]];
	sort(val+1,val+n+1);
	n=unique(val+1,val+n+1)-val-1;
	REP(i,1,n)cnt[i]=mp[val[i]];
	Pow[0]=1;
	REP(i,1,maxn-10)Pow[i]=Pow[i-1]*2%mod;
	int sq=sqrt(p);
	REP(i,1,sq)if(p%i==0){
		d[++tot]=i;
		num[i]=tot;
		if(p/i!=i)d[++tot]=p/i;
	}
}

int get_num(ll x){ return x*x<=p ? num[x] : num[p/x]+1;}

void work(){
	dp[0][get_num(p)]=1;
	REP(i,1,n)REP(j,1,tot){
		int k=get_num(__gcd(val[i],d[j]));
		dp[i][j]=(dp[i][j]+dp[i-1][j])%mod;
		dp[i][k]=(dp[i][k]+dp[i-1][j]*(Pow[cnt[i]]-1))%mod;
	}
	REP(i,1,tot)REP(j,1,tot)
		if(d[j]%d[i]==0)
			ans[j]=(ans[j]+dp[n][i])%mod;
	REP(i,1,q)write(ans[get_num(w[i])],'\n');
}

int main(){
	File();
	init();
	work();
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值