【校内模拟】A(容斥原理)(数位DP)(范德蒙德恒等式)(高精度)

36 篇文章 0 订阅
15 篇文章 0 订阅

简要题意:

请你对满足下列条件的正整数序列 A 1 , A 2 ⋯ A n A_1,A_2\cdots A_n A1,A2An 进行计数。

  1. ∀ 1 ≤ i ≤ n , L i ≤ A i ≤ R i \forall 1\leq i\leq n,L_i\leq A_i\leq R_i 1in,LiAiRi
  2. S = ∑ i = 1 n A i S=\sum\limits_{i=1}^nA_i S=i=1nAi 给出 D D D 进制数字集合 T T T ,满足要求的 S S S D D D 进制拆分中的所有数字都应该属于 T T T 集合。

n ≤ 12 , D ≤ 500 , 1 ≤ L i ≤ R i ≤ D 500 n\leq 12 ,D\leq 500,1\leq L_i\leq R_i\leq D^{500} n12,D500,1LiRiD500


题解:

首先高精度是跑不掉的了。。。

由于 n n n 很小,可以考虑把问题转化成只有下界进行容斥。

S S S 为所有下界之和+1 ,假设总和为 V V V ,由于没有上界,显然方案数就是 ( V − S n − 1 ) {V-S\choose n-1} (n1VS) ,用隔板法考虑即可。

那么每一次要求的东西就是 ∑ V = S I N F ( V − S n − 1 ) [ V 合 法 ] \sum\limits_{V=S}^{INF}{V-S\choose n-1}[V 合法] V=SINF(n1VS)[V]

考虑范德蒙德恒等式 : ( n + m k ) = ∑ i = 0 k ( n k ) ( m k ) {n+m\choose k}=\sum\limits_{i=0}^k{n\choose k}{m\choose k} (kn+m)=i=0k(kn)(km) ,这个等式在广义组合数上仍然成立。

于是我们只需要用数位DP,求出 ∑ V = S I N F ( V i ) [ V 合 法 ] \sum\limits_{V=S}^{INF}{V\choose i}[V合法] V=SINF(iV)[V] ,然后用 ( − S i ) {-S\choose i} (iS) 来计算即可。

DP过程中的转移也需要依赖范德蒙德恒等式。

复杂度 O ( 2 n n 2 log ⁡ D ( ∑ R ) ) O(2^nn^2\log_D(\sum R)) O(2nn2logD(R)),高精度不是复杂度瓶颈。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

using std::cerr;
using std::cout;

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<0?a-b+mod:a-b;}
inline int mul(int a,int b){ll r=(ll)a*b;return r>=mod?r%mod:r;}
inline void Inc(int &a,int b){a+=b-mod;a+=a>>31&mod;}
inline void Dec(int &a,int b){a-=b;a+=a>>31&mod;}
inline void Mul(int &a,int b){a=mul(a,b);}

cs int N=15,SIZE=527,D=5e2+7;

using Int=std::vector<int>;

int B;

Int& operator++(Int &a){
	if(!a.size())a.push_back(0);int b=1;
	for(int re i=0;i<(int)a.size();++i)
		if((a[i]+=b)>=B)a[i]-=B,b=1;else b=0;
	if(b)a.push_back(b);return a;
}

Int& operator--(Int &a){
	if(!a.size())a.push_back(0);a[0]-=1;
	for(int re i=0;a[i]<0;++i)
		a[i]+=B,a[i+1]--;
	while(a.size()&&!a.back())a.pop_back();
	return a;
}

Int gI(){
	static int len;Int a;
	static char s[1507];
	static int num[1507];
	scanf("%s",s);len=strlen(s);
	for(int re i=0;i<len;++i)
		num[len-i]=s[i]^48;
	while(len){int t=0;
		for(int re i=len;i;--i)
			t=t*10+num[i],num[i]=t/B,t%=B;
		a.push_back(t);
		while(len&&!num[len])--len;
	}return a;
}

Int &operator+=(Int &a,cs Int &b){
	if(a.size()<b.size())a.resize(b.size());
	for(int re i=0;i<(int)b.size();++i)a[i]+=b[i];
	for(int re i=0;i+1<(int)a.size();++i)
		if(a[i]>=B)a[i+1]++,a[i]-=B;
	if(a.size())while(a.back()>=B){
		int t=a.back();a.back()%=B;
		a.push_back(t/B);
	}return a;
}

int get_mod(cs Int &a){
	int res=0;
	for(int re i=a.size()-1;~i;--i)
		Mul(res,B),Inc(res,a[i]);
	return res;
}

int n,ans;
int good[D];

Int L[N],R[N],S;
int C[SIZE][D][N],Len;
int pr[SIZE][D][N],sf[SIZE][D][N];
int inv[N];

int G[N];
int F[2][2][N];

int calc(){
	int t=0,s=1,Sl=S.size()-1;
	memset(G,0,sizeof G);S.resize(Len);
	memset(F[t],0,sizeof F[t]);F[t][1][0]=1;
	for(int re i=0;i<Len;++i){
		t^=1,s^=1;memset(F[t],0,sizeof F[t]);
		for(int re x=0;x<n;++x){
			if(!F[s][0][x]&&!F[s][1][x])continue;
			int sm=add(F[s][0][x],F[s][1][x]);
			int *pr=nullptr,*sf=nullptr,*C,*f0,*f1;
			C=::C[i][S[i]],f0=F[t][0],f1=F[t][1];
			if(S[i]-1>=0)pr=::pr[i][S[i]-1];
			if(S[i]+1<B)sf=::sf[i][S[i]+1];
			for(int re y=0;x+y<n;++y){
				if(S[i]-1>=0)Inc(f0[x+y],mul(sm,pr[y]));
				if(S[i]+1<B){
					Inc(f1[x+y],mul(sm,sf[y]));
					if(i>=Sl)
						Inc(G[x+y],mul(sm,sf[y]));
				}
				Inc(f0[x+y],mul(F[s][0][x],C[y]));
				Inc(f1[x+y],mul(F[s][1][x],C[y]));
				if(i==Sl)
					Inc(G[x+y],mul(F[s][1][x],C[y]));
			}
		}
	}s=dec(0,get_mod(S));int res=0;
	for(int i=0,c=1;i<n;++i){
		if(i)Mul(c,mul(dec(s,i-1),inv[i]));
		Inc(res,mul(G[n-1-i],c));
	}return res;
}

void Main(){
	scanf("%d%d",&n,&B);
	for(int re i=0;i<B;++i)
		scanf("%d",good+i);
	for(int re i=0;i<n;++i)
		L[i]=gI(),--L[i],R[i]=gI(),S+=R[i];
	Len=S.size()+2;inv[0]=inv[1]=1;
	for(int re i=2;i<n;++i)inv[i]=mul(mod-mod/i,inv[mod%i]);
	for(int i=0,pw=1;i<Len;++i,Mul(pw,B)){
		for(int re d=0;d<B;++d)
			if(good[d]){
				C[i][d][0]=1;for(int re j=1;j<n;++j)
				C[i][d][j]=mul(mul(C[i][d][j-1],inv[j]),dec(mul(pw,d),j-1));
			}
		for(int re d=0;d<B;++d)for(int re j=0;j<n;++j)
			pr[i][d][j]=add(d==0?0:pr[i][d-1][j],C[i][d][j]);
		for(int re d=B-1;~d;--d)for(int re j=0;j<n;++j)
			sf[i][d][j]=add(d==B-1?0:sf[i][d+1][j],C[i][d][j]);
	}
	for(int re s=0;s<(1<<n);++s){
		S.clear();S.push_back(1);int ct=0;
		for(int re i=0;i<n;++i)
			(s&(1<<i))?(++ct,S+=R[i]):(S+=L[i]);
		ct&1?Dec(ans,calc()):Inc(ans,calc());
	}cout<<ans<<"\n";
}

inline void file(){
#ifdef zxyoi
	freopen("A.in","r",stdin);
#endif
}signed main(){file();Main();return 0;} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值