您的位置:首页 > 其它

poj 1173 具有重复的组合和容斥原理

2010-08-12 00:04 204 查看
题目描述

计算BC(n,k,m),就是X1+X2+...+Xk=n,其中1<=Xi<=m,有多少个解?这些解顺序排列,输入一个排列,计算它是第几个?

详见http://acm.pku.edu.cn/JudgeOnline/problem?id=1173。

如BC(7,4,2),有16个解

0:1000100|8:1100100
1:1000110|9:1100110
2:1001000|10:1101000
3:1001100|11:1101100
4:1001110|12:1101110
5:1011000|13:1110010
6:1011100|14:1110100
7:1100010|15:1110110

然后输入

743
5
1001110
1110110
1001100
1001110
1000100

输出

16
4
15
3
4
0

题目分析

IOI的题,做的我好爽呀。很多解题报告用观察的方法,我是通过组合数学分析的,也算是另一个思路吧。而且我相信从数学原理分析问题,
分析的更透彻,对本质把握的更好一些。
首先具有重复的组合数。若从k种物品中选出n个物品,其中每种物品可以原则无限多个,成为具有重复的组合数,
这个组合数等于C(n+k-1,k-1).
这个题等价于简单的数学表达是X1+X2+...+Xk=n,其中Xi>=0,有多少个解.
证明:把n个物品排成一排,用k-1个栏杆对它们进行划分,如下图所示
。。。|。。。|。。。
。||。。。。。。。。
这里n=9,k=3,第一个划分对应3+3+3=9;第二个划分对应1+0+8=9.划分和解是一一对应。
划分有C(n+k-1,k-1)种,所以解有C(n+k-1,k-1)种。
其次,容斥原理。集合A1不具有性质P1,集合A2不具有性质P2,...,那么不具有性质P1和P2...的集合中的元素的个数满足...
原谅我懒得把这个公式打出来,如果你想知道,网上随便就搜出来了。
那么回到本题,X1+X2+...+Xk=n,其中1<=Xi<=m的解的个数怎么计算呢?(1)
首先用Xi=Yi+1代替上式,得到Y1+Y2+...+Yk=n-k,其中0<=Yi<=m-1.
现在考虑式子Z1+Z2+...+Zk=n-k,其中Z0>=m;Zi>=0(i!=1)(2)
用A1表示解中不具有0<=Z1<=m-1的性质,但是其它解可以任取非负值,那么就是(2)的解。
要解(2)式,用Z1=W1+m代替,则W1可取任意值,这转化为可以求解的问题。
(2)式可以计算了,根据容斥原理就可以解(1)式。
至于排序的问题,观察观察就出来了,详情就看代码吧。

代码

#include<cstdio> #include<cmath> #include<algorithm> #include<float.h> usingnamespacestd; #defineM104 typedefunsigned__int64UINT64; intnum; intg_n,g_k,g_m; intinput[M][36]; intg_number[36]; intg_divider[36]; UINT64Combination(intn,intk) { //stillworkwhenn==k,reurn1 k=min(k,n-k); for(inti=0;i<=k;i++) { g_number[i]=n-i; g_divider[i]=i; } for(inti=2;i<=k;i++) { for(intj=0;j<k;j++) { if(g_number[j]%i==0){ g_number[j]/=i; g_divider[i]=1; break; } } } UINT64nRes=1; for(inti=0;i<k;i++){ nRes*=g_number[i]; } for(inti=2;i<=k;i++){ nRes/=g_divider[i]; } returnnRes; } intFunc(intn,intk,intm) { n=n-k; //Xi=Yi+1 UINT64nRes=Combination(n+k-1,k-1); intnSign=-1; intnTmp; for(inti=1;i<=k;i++) { nTmp=n-i*m; if(n-i*m<0) break; nRes+=nSign*Combination(k,i)*Combination(nTmp+k-1,k-1); nSign*=-1; } return(int)nRes; } intGetMinOnes(intnHandled,intkLeft) { returnmax(g_n-nHandled-(kLeft)*g_m,1); } intGetMaxZeros(intnHandled,intkLeft) { returnmin(g_n-nHandled-(kLeft),g_m); } intGetSequence(intnth) { intnLen; intidx=0; intnRes=0; intnHandled=0; inti; for(i=1;i<=g_k-2;i++) { //odd if(i%2==1){ nLen=0; while(input[nth][idx++]!=0) nLen++; idx--; for(intj=GetMinOnes(nHandled,g_k-i);j<=nLen-1;j++) { nRes+=Func(g_n-nHandled-j,g_k-i,g_m); } nHandled+=nLen; } //even else{ nLen=0; while(input[nth][idx++]!=1) nLen++; idx--; for(intj=GetMaxZeros(nHandled,g_k-i);j>nLen;j--) { nRes+=Func(g_n-nHandled-j,g_k-i,g_m); } nHandled+=nLen; } } //odd if(i%2==1){ nLen=0; while(input[nth][idx++]!=0) nLen++; nRes+=nLen-GetMinOnes(nHandled,g_k-i)+1; } //even else{ nLen=0; while(input[nth][idx++]!=1) nLen++; nRes+=GetMaxZeros(nHandled,g_k-i)-nLen+1; } returnnRes-1; } intmain() { //freopen("ReadMe.txt","r",stdin); scanf("%d%d%d",&g_n,&g_k,&g_m); scanf("%d",&num); charbuf[36]; for(inti=0;i<num;i++) { scanf("%s",buf); for(intj=0;j<g_n;j++){ input[i][j]=buf[j]-'0'; } } printf("%d/n",Func(g_n,g_k,g_m)); for(inti=0;i<num;i++) { printf("%d/n",GetSequence(i)); } return0; }[/code]



                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: