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);
    }
    
  • 相关阅读:
    MS CRM 2011 RC中的新特性(4)——活动方面之批量编辑、自定义活动
    最近的一些有关MS CRM 2011的更新
    MS CRM 2011 RC中的新特性(6)——连接
    MS CRM 2011 RC中的新特性(7)—仪表板
    参加MS CRM2011深度培训课程——第一天
    MS CRM 2011插件调试工具
    MS CRM2011实体介绍(四)——目标管理方面的实体
    MS CRM 2011 RC中的新特性(3)——客户服务管理方面
    MS CRM 2011 RC中的新特性(8)—数据管理
    ExtAspNet 登陆
  • 原文地址:https://www.cnblogs.com/ctmlpfs/p/14653592.html
Copyright © 2011-2022 走看看