Kor (數學題...)

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;
}
發佈了308 篇原創文章 · 獲贊 25 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章