Kor (数学题...)

来源:互联网 发布:程序员软件工程师 编辑:程序博客网 时间:2024/06/03 18:18

kor

10.19

思路:
考虑维护cnt数组,cnt[i]表示是i的数有几个。
考虑维护从cnt1数组,cnt1[i]表示是i的二进制子集的数有几个。
显然cnt1可以从cnt转移过来,但是为了优化时间复杂度,我们选择把cnt和cnt1合并为一个数组用2^20*20的时间处理出来。
代码如下

void sumup() {    for(int i=0; i<P; i++) {        int s = ((1<<P) - 1) ^ (1<<i);        for(int ss = s; ss >= 0; ss=(ss-1)&s) {            cnt[ss | (1<<i)] += cnt[ss];            //cnt[ss] += cnt[ ss| ( 1 << i ) ] ;            fix(cnt[ss | (1<<i)]);            if( !ss ) break;        }    }}

枚举每一位,把每一位为0的cnt累加到此位为1的cnt中。
对于单个的数进行考虑(如15),cnt1[15] += cnt[i] i为15的子集。这里写图片描述
有关15的转移(每个数在加入15之前的转移,之后再怎么变化都跟15无关了)大致如上图,一看就很有道理有没有,怎么证明呢?
考虑我们for位数时是从低位往高位枚举的(枚举把哪一位的1消掉),15加上了所有在某一位上比它少1的数的cnt,如果每个数在枚举第i位时被加入了15(如11,i=3),那么在此之前它(11)一定加上了所有在某一位x(x<=i)上比它(11)少1的数的cnt(10, 9),而这些数(10, 9)是在第i-1(2)位被加入(11)的,那么在此之前它们(10, 9)一定加上了所有在某一位x(x<=i-1)上比它们(10, 9)少1的数的cnt(如cnt[9] += cnt[8])。
这样:
1.我们一定统计的数一定合法。
因为每个数统计的都是它的某一位上1变为0的cnt,所有统计到的一定是子集。
2.我们一定统计完了所有合法的数。
因为我们统计了每个数的所有后继状态(某一位上1变为0的状态)。
3.对于任意一个数我们一定没有重复统计。
我们按照1的个数将所有数进行分层操作:
15
14 13 11 7
12 10 9 6 5 3
8 4 2 1
那么显然不同层的数之间是不会相互影响的,每个数的cnt只会加到某一位上比它多1的数的cnt里。
考虑同一层的数,用第2层举例,它们加入15是有先后顺序的(从低位到高位某个1变为0)
14 1110
13 1101
11 1011
7 0111
而每一个数我们只考虑它加入15之前的变化,14在i=1(枚举的位数)时就加入15了,并没有任何的操作,13在i=2时加入15,所以x位(x < i)为0的情况已经被统计过了,容易发现一个数统计的数都是去掉某些x位上的0(x < i)得到的。也就是说y位上的数(y >= i)是不会改变的,也就是说13产生的数在第i位(i=2)上都是0,而比它大的数(14)产生的数在第i位(i=2)上都是1。而y位上的数(y >= i)一定是一样的,所以比它大的数(14)产生的数都大于它(13)产生的数,所以同层的不同数产生的数并无交集。而每个数显然不会加上多个相同的后继,所以对于任意一个数我们一定没有重复统计。
这样就能证明算法的正确性了。
可能有人觉得,15(1111)都是1太特殊,其实对于任意一个数,我们讨论子集的时候只需要管它为1的二进制位,(111100101)可直接看做(111111),只要之后的数都与它对应就好了。
还是给出关于14的转移看看吧。
这里写图片描述

处理完cnt之后,就要处理ans了。
C(cnt, k) 并不是ans。如(cnt[15], k)。
这些方案组成的(|)不只是15(1111),还会有15的子集如1101,0010等等。这里就要用到容斥的思想了。
(用二进制表示)
ans[1111] = cnt[1111] - cnt[1110] - cnt[1101] - cnt[1011] - cnt[0111] + cnt[1100] + cnt[1010] + cnt[1001] + cnt[0110] + cnt[0101] + cnt[0011] - cnt[1000] - cnt[0100] - cnt[0010] - cnt[0001]。
代码如下:

void sumdown() {    for(int i=0; i<P; i++) {        int s = ((1<<P) - 1) ^ (1<<i);        for(int ss = s; ss >= 0; ss=(ss-1)&s) {            cnt[ss | (1<<i)] -= cnt[ss];            fix(cnt[ss | (1<<i)]);            if( !ss ) break;        }    }}

其实和上面是差不多的,只需要改成减法即可,就不再赘述啦。

还有一道kand的题目是&,只需要改成第一个代码片里//的部分就好啦。

纯手打呀~~~不容易不容易。。。(手残图丑,莫怪)

#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>using namespace std;const int N = 1e5 + 10;const int Mod = 1e9 + 7;const int P = 20;int n, k, r;int aa[N];int cnt[1<<P];int fac[N], vfac[N];int mpow(int a, int b) {    int rt;    for(rt = 1; b; b>>=1,a=(1LL*a*a)%Mod)        if(b&1) rt=(1LL*rt*a)%Mod;    return rt;}void init(int n) {    fac[0] = 1;    for(int i = 1; i <= n; i++)        fac[i] = 1LL * fac[i-1] * i % Mod;    vfac[n] = mpow(fac[n], Mod - 2);    for(int i = n - 1; i >= 0; i--)        vfac[i] = 1LL * vfac[i+1] * (i + 1) % Mod;}int comb(int n, int m) {    if(m > n) return 0;    return 1LL * fac[n] * vfac[m] % Mod * vfac[n-m] % Mod;}void fix(int &a) {    while(a >= Mod) a -= Mod;    while(a < 0) a += Mod;}void sumup() {    for(int i=0; i<P; i++) {        int s = ((1<<P) - 1) ^ (1<<i);        for(int ss = s; ss >= 0; ss=(ss-1)&s) {            cnt[ss | (1<<i)] += cnt[ss];            //cnt[ss] += cnt[ ss| ( 1 << i ) ] ;            fix(cnt[ss | (1<<i)]);            if( !ss ) break;        }    }}void sumdown() {    for(int i=0; i<P; i++) {        int s = ((1<<P) - 1) ^ (1<<i);        for(int ss = s; ss >= 0; ss=(ss-1)&s) {            cnt[ss | (1<<i)] -= cnt[ss];            fix(cnt[ss | (1<<i)]);            if( !ss ) break;        }    }}int main() {    freopen("kor.in", "r", stdin);    freopen("kor.out", "w", stdout);    int T; scanf("%d", &T);    init(1e5);    while(T--) {        memset(cnt, 0, sizeof(cnt));        scanf("%d%d%d", &n, &k, &r);        for(int i = 1; i <= n; i++) {            scanf("%d", aa + i);            cnt[aa[i]]++;        }        sumup();        for(int s=0; s<(1<<P); s++)             cnt[s] = comb(cnt[s], k);        sumdown();        printf("%d\n", cnt[r]);    }    return 0;}
原创粉丝点击