HDOJ--4869--Turn the pokers【组合数学+快速幂】
题意:有m张扑克,开始时全部正面朝下,你可以翻n次牌,每次可以翻xi张,翻拍规则就是正面朝下变背面朝下,反之亦然,问经过n次翻牌后牌的朝向有多少种情况。我们可以把正面朝上理解为1,反面朝上理解为0,那么可以理解为求01串的不同的组合方式有几种。
解题思路:我们可以知道,每张牌假设起始状态都为0,如果翻奇数次,该牌最后的情况是1,如果翻偶数次,该牌的最后情况为0.根据n次翻牌的个数找出1的个数的下限和上限,然后再在这个范围里用组合数学求有几种排列即可。
我们先来看怎么求1的个数的上限和下限:
Minm:下限 maxm:上限 p:当前的下限 q:当前的上限
1.求1的个数的下限:
当前下限大于等于现在翻牌的数量,这个比较好理解,全翻1,1变成0,则剩下的1就是minm-x;
当前下限小于翻牌数量,上限大于等于翻牌数量,即翻牌数量刚好在上下限之间,所以最少可以把正面朝上的数量减为零,但不一定能减到0,因为有可能当前正面朝上的牌时奇数,而翻牌数量是偶数,所以要判断奇偶性是否一样,为什么要和minm比较奇偶性。如果奇偶性相同,可以减为0,
否则,应该为1。
翻牌数量比上限还大的时候,直接减去上限就是下限。
2.求1的个数的上限:
上限+翻牌数量没有达到总牌数时,上限+翻牌数量就是新的上限,全翻0,这样使1最多;
上限+翻牌数量大于总牌数,而下限+翻牌数量小于等于总牌数,前者可以说是翻牌溢出了,已经全是1再翻的话只会让一些1变成0,后者没有达到全变成1的情况。它们是一个上限一个下限,这说明可以处理到在这之间的情况,那么最好的结果是所有牌都正面朝上,全是1,需要判断奇偶性是否一致,这回和m比较。
上限+翻牌数、下限+翻牌数全都大于总牌数时,说明都会溢出,那就用2 * m - (x + minm)来表示上限,因为(x+minm)小,所以溢出的1变成0的牌数少。
接下来要处理的是计算出 c ( m , i ) 的值:
由于数比较大,要对1000000009取余,这里要用到快速幂取余:
模板如下:
- long long PowerMod (int a, int b, int c)
- {
- int ans = 1;
- a = a % c;
- while(b>0) {
- if(b % 2 = = 1)
- ans = (ans * a) % c;
- b = b/2; // b>>=1;
- a = (a * a) % c;
- }
- return ans;
- }
关于快速幂取余,可以查看链接:http://blog.csdn.net/acm_code/article/details/38270829
用数组c表示组合数学 c ( m , i ) 的值, 按理说 c[ i ] = c[ i - 1 ] * ( m - i + 1 ) / i ,然后这个数对MOD取模,但是存在除法取模就不是这么简单的分解了,费马小定理是这样: a^(p-1) ≡1(mod p),p为质数,a、p互质,a^(p-1) mod p 恒等于1。
变换一下,两边同时除以a ,变成 a^(p-2)=a^(-1)(mod p),所以要除以a 就可以表示成 乘 a^(p-2),所以有了这样的写法:c[i] = c[i-1] * (m-i+1) % MOD * PowerMod(i,MOD-2) % MOD;
最后将组合数学值相加的时候,要隔一个相加,不难发现上限和下限的奇偶性一样。
总和一下上述思想,我们可以写出代码:
1 #include2 #define MOD 1000000009 3 __int64 c[100100]; 4 __int64 mode(__int64 a,int n) 5 { 6 __int64 t = a; 7 __int64 ans = 1; 8 while(n) 9 { 10 if(n & 1) 11 { 12 ans = ans * t % MOD; 13 } 14 n >>= 1; 15 t = t * t % MOD; 16 } 17 return ans; 18 } 19 int main() 20 { 21 int minm,maxm,x; 22 int p,q; 23 int n,m; 24 while(scanf("%d%d",&n,&m)!=EOF) 25 { 26 //初始状态下1的上限和下限都为0; 27 minm = maxm = 0;//maxm表示上限,minm表示下限 28 p = q = 0;//p、q分别记录当前下,上限,然后更新到minm、maxm中 29 for(int i=0; i ) 30 { 31 scanf("%d",&x);//第i次翻牌的数量 32 //判断下限 33 if(minm>=x)//当前下限大于等于现在翻牌的数量 34 p = minm - x;//全翻1,1变成0,则剩下的1就是minm-x 35 36 else if(maxm>=x)//当前下限小于翻牌数量,上限大于等于翻牌数量, 37 p = ((x&1)==(minm&1))?0:1; //x与minm同奇偶就为0 ,否则为1 38 39 else//翻牌数量比上限还大的时候,直接减去上限就是下限 40 p = x - maxm; 41 42 //判断上限 43 if(maxm+x<=m)//上限+翻牌数量没有达到总牌数时,上限+翻牌数量就是新的上限 44 q = maxm + x; 45 46 else if(minm+x<=m) //上限+翻牌数量大于总牌数,而下限+翻牌数量小于等于总牌数 47 q = (((minm+x)&1)==(m&1))?m:m-1;//x与minm同奇偶就为0,否则为1 48 49 else//上限+翻牌数、下限+翻牌数全都大于总牌数 50 q = 2 * m - (x + minm); 51 52 minm = p; 53 maxm = q; 54 } 55 __int64 sum=0; 56 c[0]=1; 57 for(int i=1; i<=maxm; i++)//求C(m,i); 58 { 59 if(m-i<i) 60 c[i] = c[m-i]; 61 else 62 { 63 c[i] = c[i-1]*(m-i+1)%MOD*mode((__int64)i,MOD-2)%MOD; 64 } 65 } 66 for(int i=minm; i<=maxm; i+=2) 67 { 68 sum+=c[i]; 69 sum%=MOD; 70 } 71 printf("%I64d\n",sum); 72 } 73 return 0; 74 }