ACM-排列组合

来源:互联网 发布:js判断input是否选中 编辑:程序博客网 时间:2024/05/18 07:26

话说排列组合博大精深,看来确实是的,各种驾驭不了的公式、定理啊!

组合,简单的说就是从n个东西里面,挑出k个来,有多少种挑法。那么,我们要如何方便的求出组合数c[n][k](代表从n个里面取k个的不同方法)呢?据说杨辉三角可以解决!其实杨辉三角就是一堆有规律的数字,刚好它的规律就是二项式(a+b)^n展开后的系数,而这些系数也是有规律的,那就是二项式定理,刚好这个定理里面就涉及到了组合数c[n][k]。倒推回去,意思就是说,我们可以根据杨辉三角算出组合数来。具体过程参考入门经典183页,下面给出求组合数的代码:

#define MOD 10000const int MAXN = 100; // 组合上限int c[MAXN][MAXN];    // 组合数void GetGroup(){    c[0][0] = c[1][0] = c[1][1] = 1;    for (int i=2; i<MAXN; ++i)    {        c[i][0] = 1;        for (int j=1; j<=i; ++j)             c[i][j] = (c[i-1][j] + c[i-1][j-1]) % MOD;  // 求模,防止结果过大    }    return ;}

上面的程序就求出了C[MAXN][MAXN]以内的任意组合数,有点类似于打表。还有一点需要注意的就是,有些题目给出的数据比较大,如果取模对结果不影响的话,就可以在代码所示位置进行求模运算。

接下来,写一道与组合有关的题吧,HDOJ:4810,时空转移(点击打开链接),题目如下:

Wall Painting

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 1190    Accepted Submission(s): 334


Problem Description
Ms.Fang loves painting very much. She paints GFW(Great Funny Wall) every day. Every day before painting, she produces a wonderful color of pigments by mixing water and some bags of pigments. On the K-th day, she will select K specific bags of pigments and mix them to get a color of pigments which she will use that day. When she mixes a bag of pigments with color A and a bag of pigments with color B, she will get pigments with color A xor B.
When she mixes two bags of pigments with the same color, she will get color zero for some strange reasons. Now, her husband Mr.Fang has no idea about which K bags of pigments Ms.Fang will select on the K-th day. He wonders the sum of the colors Ms.Fang will get with different plans.

For example, assume n = 3, K = 2 and three bags of pigments with color 2, 1, 2. She can get color 3, 3, 0 with 3 different plans. In this instance, the answer Mr.Fang wants to get on the second day is 3 + 3 + 0 = 6.
Mr.Fang is so busy that he doesn’t want to spend too much time on it. Can you help him?
You should tell Mr.Fang the answer from the first day to the n-th day.
 

Input
There are several test cases, please process till EOF.
For each test case, the first line contains a single integer N(1 <= N <= 103).The second line contains N integers. The i-th integer represents the color of the pigments in the i-th bag.
 

Output
For each test case, output N integers in a line representing the answers(mod 106 +3) from the first day to the n-th day.
 

Sample Input
41 2 10 1
 

Sample Output
14 36 30 8
 

题意:

给出n个数,并且也代表着总共有n天,第k天就从这n个数中选出k个数进行异或,然后将所有不同方案的异或值相加输出即可。

分析:

要解决这道题,我们需要解决两个问题,一是组合问题(很明显的n选k),二是异或后相加问题。首先求组合数的问题,可以利用上面给出的方法,因为n最大为1000,可以先打表求出组合数;其次是异或,试想如果我们直接按照题目说的那样,先求出各种组合,再直接异或,最后再相加求和,那得多麻烦啊,还不好写,那有没有其它的办法呢?我们知道异或运算是位运算,并且规则是两两不同为一,否者为零,那么根据这个性质,又可以得出一个等式:a xor b = x1<<(L-1) + x2<<(L-2) + ... + xj<<0(其中xj是a和b对应位的按位异或值,<<是左移运算,L为a和b的二进制位数),其实就是对应位异或后乘上对应位的权重(2^j)。举个例子:0001(1)xor 0010(2)= 0<<3 + 0<<2 + 1<<1 + 1<<0 = 3。也就是说,我们将整体上的异或拆成了对每一位上的运算。那我们这样转换有什么用呢?其实,好处就是这样改造异或运算后可以和组合联系起来,并且同时计算,就不用先求组合再求异或和了。如何联系?大家可以想象,将异或写成了多项和的形式后,求异或就可以离散的求了,因为是多项的和,并且我们发现每一项都只与某一特定的二进制位相关,那么意味着可以对每一个二进制位单独讨论后再求和,而对于单独某一位的组合数,是不是就很好求了。所以,我们最终得出结论:ans = (c[a1][i]*c[b1][k-i])*(x1<<(L-1)) +(c[a2][k]*c[b2][k])*( x2<<(L-2)) + ... + (c[aj][k]*c[bj][k])*(xj<<0)(其中aj和bj分别代表对应位上1的个数和0的个数,因为只有1 xor 0才不为零,c[][]是组合数,k是第几天)。最后一步就是如何求ai和bi了,其实只需要统计一下每一位上1的个数求出ai就行了,bi=n-ai。哦,对了,还有别忘了求模,数据可能会变得很大。

源代码:

#include <cstdio>#include <cstring>#define MOD 1000003#define LL long longconst int MAXN = 1003;     // 组合上限int c[MAXN][MAXN];         // 组合数int num[MAXN], ans[MAXN];void GetGroup (){    c[0][0] = c[1][0] = c[1][1] = 1;    for (int i = 2; i < MAXN; i++)    {        c[i][0] = 1;        for (int j=1; j<=i; ++j)            c[i][j] = (c[i-1][j] + c[i-1][j-1]) % MOD;    }    return ;}void func (int data)       // 统计每一位上1的个数{    for (int i=0; i<32; ++i)        if (data & (1<<i))            ++num[i];}int main (){    int n;    GetGroup ();    while(~scanf ("%d", &n))    {        int tmp;        memset (num, 0, sizeof(num));        memset (ans, 0, sizeof(ans));        for (int i=0; i<n; ++i)        {            scanf("%d", &tmp);            func(tmp);        }        for(int k=1; k<=n; ++k)  // 根据推出的结论算出每天对应的答案            for(int j=0; j<32; ++j)                for(int i=1; i<=num[j]&&i<=k; i+=2)                    ans[k] = (ans[k]+(LL)c[num[j]][i]*c[n-num[j]][k-i]%MOD*(1<<j)%MOD)%MOD;        for(int k=1; k<=n; ++k)            printf("%d%c", ans[k], k==n ? '\n' : ' ');    }    return 0;}

关于组合数就先说到这吧,后面有了新东西再过来补充,其它类似的题目还有,HDOJ:4045。

0 0
原创粉丝点击