zoukankan      html  css  js  c++  java
  • 组合数学练习

    bzoj1042
    用容斥。
    由于硬币总数不多,枚举打破硬币个数限制的集合。
    我们需要求出钦定一些硬币数(>a_i),其它任意的集合。
    把总数(-=sum_{iin S}a_ic_i)后就变成了求恰好组成(v)的方案数。
    用完全背包求出。

    #include<bits/stdc++.h>
    using namespace std;
    #define N 100010
    #define int long long
    int c[10],d[10],T,s,f[N];
    signed main(){
    	f[0]=1;
    	for(int i=0;i<4;i++)
    		scanf("%lld",&c[i]);
    	scanf("%lld",&T);
    	for(int i=0;i<4;i++)
    		for(int j=0;j<N;j++)
    			if(j>=c[i])
    				f[j]+=f[j-c[i]];
    	while(T--){
    		for(int i=0;i<4;i++)
    			scanf("%lld",&d[i]);
    		scanf("%lld",&s);
    		int ans=0;
    		for(int i=0;i<16;i++){
    			int v=s;
    			for(int j=0;j<4;j++)
    				if(i&(1<<j))
    					v-=c[j]*(d[j]+1);
    			int t=1;
    			for(int j=0;j<4;j++)
    				if(i&(1<<j))
    					t=-t;
    			if(v>=0)
    				ans+=f[v]*t;
    		}
    		printf("%lld
    ",ans);
    	}
    }
    

    [FJOI2017]矩阵填数
    发现每个点的答案上界是它被覆盖到的矩形的值的最小值。
    但是如果直接这样子算会算重。
    由于(n)很小,考虑容斥,枚举(< v)的矩形的集合,然后运行上面的算法。
    然而由于(H,W)很大,会TLE。
    考虑离散化,把整个矩形按照限制的矩形的边界切开,如果存在(x=v)的边界,则把矩形在(x=v)处切开,(y)同理。
    对于一个连通的区域,所有限制都是一样的,可以用快速幂计算。

    ARC093D
    感觉倒着填数很妙,虽然还是自己想出来了。
    (1)的顺序对答案没有影响,可以钦定(1)在二叉树最左边,最后答案乘以(2^n)
    考虑容斥,(1)到达根节点的条件:(1)到根所有节点的右节点的数(子树最小值)都不能打败(1)
    枚举(1)到根的子树的右节点能够打败(1)的集合,设(f_s)表示钦定根的右节点能够打败(1)的集合,答案就是(sum_{s}(-1)^sf_s)
    (f)可以dp求。
    如果正着dp,则发现我们不能知道后面的状态,不可行。
    考虑倒着状压dp,设(g_{i,s})表示倒着填到第(i)个,填了(s)集合内的数的方案。
    注意到(s)事实上蕴含了后面填的空位个数这个条件。
    转移:1.(i)不填,(g_{i,s}+=g_{i+1,s})
    2.(i)填,枚举填的数(p),后面还没有填的数有(2^n-a_i-s)个,可以用组合数算出选出(2^k-1)个的方案数。
    还要在子树上任意排列这些数,方案是((2^k)!)
    转移到(g_{i,s+2^p})
    然而事实上我们还是有一些数没有填。
    发现未填的数有((i-s))个,这些数可以任意排列,答案乘以((i-s)!)
    时间复杂度(O(nm2^n))

    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    #define N 18
    #define mo 1000000007
    int n,m,a[N],f[N][(1<<N)],jc[1<<N],ij[1<<N],ans;
    int qp(int x,int y){
    	int r=1;
    	for(;y;y>>=1,x=x*x%mo)
    		if(y&1)
    			r=r*x%mo;
    	return r;
    }
    int c(int y,int x){
    	if(y<0||x<0||y<x)
    		return 0;
    	return jc[y]*ij[x]%mo*ij[y-x]%mo;
    }
    signed main(){
    	scanf("%lld%lld",&n,&m);
    	for(int i=1;i<=m;i++)
    		scanf("%lld",&a[i]);
    	f[m+1][0]=1;
    	jc[0]=ij[0]=1;
    	int sz=(1<<N)-1;
    	for(int i=1;i<=sz;i++)
    		jc[i]=jc[i-1]*i%mo;
    	ij[sz]=qp(jc[sz],mo-2);
    	for(int i=sz;i;i--)
    		ij[i-1]=ij[i]*i%mo;
    	for(int i=m;i;i--)
    		for(int j=0;j<(1<<n);j++){
    			f[i][j]=(f[i+1][j]+f[i][j])%mo;
    			for(int k=0;k<n;k++)
    				if(!(j&(1<<k))){
    					f[i][j+(1<<k)]=(f[i][j+(1<<k)]+f[i+1][j]*c((1<<n)-a[i]-j,(1<<k)-1)%mo*jc[1<<k]%mo)%mo;
    				}
    		}
    	for(int i=0;i<(1<<n);i++){
    		int c=0,x=i;
    		while(x){
    			if(x&1)
    				c++;
    			x/=2;
    		}
    		if(c&1)
    			ans=(ans-jc[(1<<n)-1-i]*f[1][i]%mo+mo)%mo;
    		else
    			ans=(ans+jc[(1<<n)-1-i]*f[1][i]%mo)%mo;
    	}
    	printf("%lld",ans*(1<<n)%mo);
    }
    

    arc096c
    简单题
    考虑容斥,枚举打破限制的集合,集合内的元素都只能选择0/1个,其它任意。
    设打破了(i)个,(f(i))为打破(i)个答案,答案是(sum_{i=0}^n(-1)^if(i))
    考虑分解成只能选择0/1个的集合的选择方案和其他集合的选择方案。
    第一部分是(S(j,i))的和,可以递推
    第二部分事实上可以考虑剩下的数组成的集合,有(2^{2^{n-i}})种,剩下的数也可以被插入第一部分的集合,如果选择了(s(j,i))计数,则方案是((2^{n-i})^j)

    NOIP D
    考试时没有打80分裸暴力,傻逼了
    考虑经典的(>=)(=)转化,答案等于走到(i)还未结束的方案之和。
    容易发现,只有某一维走过的(min,max)才有用。
    对于每一维建立不等式数学模型,我们要求(a_i+maxleq w_i,a_i-mingeq 0)
    移项后发现(a_i)事实上是在一个区间内的。
    (k=1)答案就是区间长度。
    否则使用乘法原理,把所有区间的长度乘起来就是答案。
    暴力模拟发现只需要模拟到(max w_i)即可。
    考虑每一维的不等式,发现它在走(n)步后就会纯循环,这事实上让我们可以得到更为优秀的做法。
    暴力模拟走前(n)步的结果,考虑把所有(imod n)相同的(i)一起考虑。
    假设它在走(n)步后,第(i)维的取值有(h_i)类。
    (n)步后每走(n)步,(h_i)就会减少(e_i)
    假设有(v)个循环,我们要求(sum_{i=1}^vsum_{j=1}^k(h_j-ie_k))
    把后面的看成关于(i)的多项式(F(i)),则我们要求(sum_{i=1}^vF(i))
    用暴力多项式乘法处理出(F)后,问题转化成自然数幂和,用插值解决。
    时间复杂度(O(nk^2)),代码细节有点多
    常数巨大,卡着过的。

    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    #define mo 1000000007
    #define N 500010
    int n,k,f[20][20],g[N],h[N],jc[N],w[N],m[N],mn[N*2][11],mx[N*2][11],nw[N*2][11],e[N],ans,pl[13],sz,va[13],vv[N],c[N],d[N],ij[N];
    int qp(int x,int y){
    	int r=1;
    	for(;y;y>>=1,x=x*x%mo)
    		if(y&1)r=r*x%mo;
    	return r;
    }
    int qz(int n,int k){
    	g[0]=h[k+3]=1;
    	for(int i=k+2;i;i--)
    		h[i]=h[i+1]*(n-i)%mo;
    	for(int i=1;i<=k+2;i++)
    		g[i]=g[i-1]*(n-i)%mo;
    	int r=0;
    	for(int i=1;i<=k+2;i++){
    		int va=f[k][i]*g[i-1]%mo*h[i+1]%mo*ij[i-1]%mo*ij[k+2-i]%mo;
    		if((k-i+2)&1)r=(r-va+mo)%mo;
    		else r=(r+va)%mo;
    	}
    	return r;
    }
    void ml(int *a,int *b){
    	for(int i=0;i<=sz+1;i++)
    		vv[i]=0;
    	for(int i=0;i<=sz;i++)
    		for(int j=0;j<2;j++)
    			vv[i+j]=(vv[i+j]+a[i]*b[j]%mo)%mo;
    	for(int i=0;i<=sz+1;i++)
    		a[i]=vv[i];
    	sz++;
    }
    signed main(){
    	scanf("%lld%lld",&n,&k);
    	jc[0]=ij[0]=1;
    	for(int i=1;i<=k+4;i++){
    		jc[i]=jc[i-1]*i%mo;
    		ij[i]=qp(jc[i],mo-2);
    	}
    	for(int i=0;i<=k+2;i++)
    		for(int j=1;j<=k+2;j++)
    			f[i][j]=(f[i][j-1]+qp(j,i))%mo;
    	for(int i=1;i<=k;i++)
    		scanf("%lld",&w[i]);
    	for(int i=1;i<=n;i++){
    		scanf("%lld%lld",&c[i],&d[i]);
    	}
    	int vt=1;
    	for(int i=1;i<=k;i++)
    		vt=vt*w[i]%mo;
    	ans=(ans+vt)%mo;
    	for(int i=1;i<=n*2;i++){
    		for(int j=1;j<=k;j++){
    			mn[i][j]=mn[i-1][j];
    			mx[i][j]=mx[i-1][j];
    			nw[i][j]=nw[i-1][j];
    		}
    		int p1=c[(i-1)%n+1];
    		nw[i][p1]+=d[(i-1)%n+1];
    		mx[i][p1]=max(mx[i][p1],nw[i][p1]);
    		mn[i][p1]=min(mn[i][p1],nw[i][p1]);
    		if(i<=n){
    			vt=1;
    			for(int j=1;j<=k;j++){
    				if(w[j]>=(mx[i][j]-mn[i][j]))
    					vt=vt*(w[j]-(mx[i][j]-mn[i][j]+mo)%mo+mo)%mo;
    				else
    					vt=0;
    			}
    			ans=(ans+vt)%mo;
    		}
    	}
    	int ok=0;
    	for(int i=1;i<=k;i++){
    		e[i]=max(mx[2*n][i]-mx[n][i],mn[n][i]-mn[n*2][i]);
    		if(e[i])
    			ok=1;
    	}
    	if(!ok){
    		puts("-1");
    		return 0;
    	}
    	for(int i=1;i<=n;i++){
    		memset(pl,0,sizeof(pl));
    		sz=0;
    		pl[0]=1;
    		int vt=1,vs=0,sv=1e11;
    		for(int j=1;j<=k;j++){
    			if(e[j])
    				sv=min(sv,(w[j]-(mx[n+i][j]-mn[n+i][j]))/e[j]);
    		}
    		for(int j=1;j<=k;j++){
    			va[0]=(w[j]-(mx[n+i][j]-mn[n+i][j])+mo)%mo;
    			if(w[j]<(mx[n+i][j]-mn[n+i][j]))
    				vs=-1;
    			va[1]=(-e[j]+mo)%mo;
    			vt=vt*va[0]%mo;
    			ml(pl,va);
    		}
    		if(vs!=-1){
    			for(int j=0;j<=sz;j++)
    				ans=(ans+qz(sv,j)*pl[j]%mo)%mo;
    			ans=(ans+vt)%mo;
    		}
    	}
    	printf("%lld",ans);
    }
    

    SubterraneanSun
    简单题,考试时没有认真想。
    如果我们得到了(A,B),则可以快速求出答案,不用题解所说的fft。
    分类讨论去掉绝对值,(sum_{i=1}^ssum_{j=1}^ia_ib_j(i-j+1)^2=sum_{i=1}^ssum_{j=1}^ia_ib_j(i^2+j^2-2ij+1-j+i))
    然后把式子拆出来,以(sum_{i=1}^ssum_{j=1}^ia_ib_ji^2)为例,我们事实上可以把(i)从和式中拿出来,这样子处理出(b_j)的前缀和就能计算。
    (j>i)时同理。
    时间复杂度(O(n)),比标算更为优秀。
    再考虑如何求出(A,B),考虑求出(f_{i,j})
    (s_i=sum_{j=1}^nf_{i,j}),那么(f_{i,j}=sum_{i=1}^n(f_{i-1,k}+j^i)=s_{i-1}+nj^i)
    (s_i=sum_{j=1}^nf_{i,j}=sum_{j=1}^n(s_{i-1}+nj^i)=ns_{i-1}+nsum_{j=1}^nj^i)
    如果我们得到了(s_{n-1}),那么可以在(O(m))的时间内计算出所有需要的(a,b)(前,后(300000)个)
    (s)数组手动展开,(s_p=n^{p-1}(sum_{i=1}^nj)+sum_{i=1}^{p-1}(sum_{j=1}^nj^{p-i+1})n^i)
    括号内的自然数幂和由于(m)不大,可以插值。
    时间复杂度(O(m^2+n))

    CF865G
    花的OGF:(A(x)=(sum_{x^{p_i}})^N)
    巧克力的生成函数:(B(x)=frac{1}{1-sum_{x^{c_i}}})
    根据样例给的提示,我们要求花瓣有(i)个的方案数:(sum_i(A(x)[x^i])(B(x)[x^i]))
    如果我们知道了(i),考虑(B)的实际意义,显然是背包dp。
    (f_i)表示买了(i)块巧克力,显然有转移(f_i=sum_jf_{i-c_j})(f)的OGF就是(B)
    (f)可以常系数线性齐次递推求 ,(B(x)[x^i]=x^imod P(x))(P)是特征多项式。
    我们需要枚举(A(x))的每一项求出答案,这太慢了。
    我们要求的:(A(x)[x^i])乘以(B(x)[x^i]),就是(i)(c)拼接成的方案数。
    发现(mod P(x))事实上是线性的,也是可乘的。
    根据常系数线性齐次递推的快速算法的意义,考虑手动展开(A),把(A(x)[x^v])展开成若干项(b)使得(b_i(B(x)[x^{max(c_i)-i}])=)答案。
    这可以先求出所有(x^{p_i}mod P(x))的和,然后(N)次方。
    求出(B(x))(max(c_i))可以背包。
    感觉根据常系数线性齐次递推的快速算法的意义展开比较巧妙。

    #include<bits/stdc++.h>
    using namespace std;
    #define N 250
    #define mo 1000000007
    #define int long long
    int c[N*2+6],a[N],b[N],n,m,k,ans[N*2+6],va[N*2+6],vv[N*2+6];
    void ml(int *a,int *d){
    	for(int i=0;i<2*N;i++)
    		c[i]=0;
    	for(int i=0;i<N;i++)
    		for(int j=0;j<N;j++)
    			c[i+j]=(c[i+j]+a[i]*d[j])%mo;
    	for(int i=2*N-1;i>=N;i--){
    		for(int j=1;j<=m;j++){
    			if(i>=b[j])
    				c[i-b[j]]=(c[i-b[j]]+c[i])%mo;
    		}
    	}
    	for(int i=0;i<N;i++)
    		a[i]=c[i];
    }
    void qp(int *x,int y){
    	for(int i=0;i<N*2;i++)
    		ans[i]=0;
    	ans[0]=1;
    	for(;y;y>>=1,ml(x,x))
    		if(y&1)
    			ml(ans,x);
    }
    signed main(){
    	scanf("%lld%lld%lld",&n,&m,&k);
    	for(int i=1;i<=n;i++)
    		scanf("%lld",&a[i]);
    	for(int i=1;i<=m;i++)
    		scanf("%lld",&b[i]);
    	for(int i=1;i<=n;i++){
    		for(int j=0;j<N*2;j++)
    			va[j]=0;
    		va[1]=1;
    		qp(va,a[i]);
    		for(int j=0;j<N;j++)
    			vv[j]=(vv[j]+ans[j])%mo;
    	}
    	qp(vv,k);
    	for(int j=0;j<N;j++)
    		vv[j]=ans[j];
    	for(int i=N-1;i;i--)
    		for(int j=1;j<=m;j++)
    			if(i>=b[j])
    				vv[i-b[j]]=(vv[i-b[j]]+vv[i])%mo;
    	printf("%lld
    ",vv[0]);
    }
    

    抗拒黄泉
    (瞄了一眼题解)
    考虑经典的等于和大于等于的转化,问题转化成统计时刻(i)还没有满足要求的期望。
    (n+m)很小时考虑容斥,枚举没有选择的行/列,并且把它全部染成1。
    这样子每一回合必须选择(0),设合法选择的概率为(p)
    计算答案事实上是经典的期望公式,是(frac{1}{p})
    (n+m)比较大时就不能这么做,但是(n*mleq 200)
    考虑平衡规划,当(n<m)时,(nleq 14),那么可以容斥(n)
    枚举哪些列没有被选择,并且把它全部染成1。
    这样子每一回合必须选择(0),设合法选择的概率为(p)(p)只和现在(0)格子数量除以原来的(1)格子数量有关
    那么就可以考虑dp了,设(f_{i,j,k})表示dp到前(i)行,选了(j)行,(0)格子有(k)个的方案数。
    由于((-1)^j=(-1)^{j+2}),所以只需要记录(jmod 2)的结果。
    转移显然,最后乘以(frac{1}{k})计算答案。

    #include<bits/stdc++.h>
    using namespace std;
    #define N 210
    int n,m,a[N][N],ct,f[30][2][N],b[N][N],cc,bz[N];
    double ans;
    int main(){
    	//freopen("refuse.in","r",stdin);
    	//freopen("refuse.out","w",stdout);
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++){
    			scanf("%d",&a[i][j]);
    			ct+=a[i][j];
    		}
    	if(n>m){
    		for(int i=1;i<=n;i++)
    			for(int j=1;j<=m;j++)
    				b[j][i]=a[i][j];
    		for(int i=1;i<=m;i++)
    			for(int j=1;j<=n;j++)
    				a[i][j]=b[i][j];
    		swap(n,m);
    	}
    	for(int i=0;i<(1<<n);i++){
    		int bc=0;
    		cc=0;
    		memset(f,0,sizeof(f));
    		memset(bz,0,sizeof(bz));
    		for(int j=1;j<=n;j++)
    			if(i&(1<<(j-1))){
    				bz[j]=1;
    				for(int k=1;k<=m;k++)
    					cc+=a[j][k];
    				bc^=1;
    			}
    		f[0][0][cc]=1;
    		for(int j=0;j<m;j++){
    			int cv=0;
    			for(int k=1;k<=n;k++)
    				if(!bz[k])
    					cv+=a[k][j+1];	
    			for(int k=0;k<2;k++)
    				for(int l=cc;l<=ct-cv;l++){
    					f[j+1][k][l]+=f[j][k][l];
    					f[j+1][k^1][l+cv]+=f[j][k][l];
    				}
    		}
    		for(int l=1;l<=ct;l++){
    			double cf=((double)ct/(double)l);
    			if(!bc){
    				ans+=f[m][1][l]*cf;
    				ans-=f[m][0][l]*cf;
    			}
    			else{
    				ans+=f[m][0][l]*cf;
    				ans-=f[m][1][l]*cf;
    			}
    		}			
    	}
    	printf("%.5lf
    ",ans);
    }
    
  • 相关阅读:
    SQL解发器与SQL游标实例
    动态调用JS
    HDU_5729_rmq+二分
    struts2 在MyEclipse中 的配置
    Struts 1.2 中如何测试Action
    OGNL使用小结【转】
    JUnit中assertEquals和assertSame方法的不同
    struts2 ActionContext
    ser文件与Java对象序列化
    测试Action组件代码(StrutsTestCase)
  • 原文地址:https://www.cnblogs.com/ctmlpfs/p/14653592.html
Copyright © 2011-2022 走看看