FFT解决组合数问题

FFT可以解决这样一种问题:

有k种元素,均有无穷多个,规定第i种元素选取的个数ci必须属于一个特定的集合Si,当需要选取r个元素时,有多少种选取方式?

Exp:有苹果,香蕉和桃子3种水果,如果苹果只能选不超过3个,选香蕉的个数必须是5的倍数,而桃子的个数必须是素数,问选r个水果有几种方法。

Solution:解决方法是把每个集合写成一个多项式,使得每一项x^i的系数取决于i是否在集合中存在:如果存在,则系数等于1;否则,系数等于0。这样苹果对应的多项式是1+x+x2+x3…香蕉x5+x10+x15…桃子x2
+x3+x5+x7…然后把多项式乘起来,就行了,结果中xr的系数就是选r个元素的方法数。

FFT 就可以把多项相乘n方复杂度降为nlogn

例题

Super Poker II UVA - 12298
题目大意:
有一副超级扑克牌,超级扑克牌由四个花色组成,每个花色都有无数张牌,扑克牌的面值p满足条件:p的约数的个数大于2,即扑克牌的面值为:4,6,8,9,10,12,…(不包括1和素数的自然数) 现在这幅扑克牌丢失了c张,问你从剩余的扑克牌的4个花色中各选出一张牌来,四个花色的面值之和组成一个新的值,问分别有多少种选法能组成值a, a+1, a+2,…,b。 输出b-a+1行,每行一个数代表选法的个数。
思路:
跟选水果的那个思路一样,这里是四个多项式相乘,注意开数组大小。

#include <bits/stdc++.h>
using namespace std;
// -------------------- FFT --------------------/ 
typedef long double db;
const db PI = acos(-1.0);
struct Complex {
    db x, y;
    Complex(db x=0.0, db y=0.0) : x(x), y(y) {}
    Complex operator - (const Complex &b) const {
        return Complex(x-b.x, y-b.y);
    }
    Complex operator + (const Complex &b) const {
        return Complex(x+b.x, y+b.y);
    }
    Complex operator * (const Complex &b) const {
        return Complex(x*b.x-y*b.y, x*b.y+y*b.x);
    }
};
void change(Complex y[], int len) {
    for(int i = 1, j = len / 2; i < len - 1; i++) {
        if(i < j) swap(y[i], y[j]);
        int k = len / 2;;
        while(j >= k) {
            j -= k;
            k /= 2;
        }
        if(j < k) j+=k;
    }
}
void fft(Complex y[], int len, int on) {
    change(y, len);
    for(int h = 2; h <= len; h<<=1) {
        Complex wn(cos(-on*2*PI/h), sin(-on*2*PI/h));
    
        for(int j = 0; j < len; j+=h) {
            Complex w(1, 0);
            for(int k = j; k < j + h / 2; k++) {
                Complex u = y[k];
                Complex t = w*y[k+h/2];
                y[k] = u + t;
                y[k+h/2] = u - t;
                w = w*wn;
            }
        }
    }
    if(on == -1) {
        for(int i = 0; i < len; i++) {
            y[i].x /= len;
        }
    }
}
// -------------------------FFT-------------------------/
int idx(char c) {
    if(c == 'S') return 0;
    else if(c == 'H') return 1;
    else if(c == 'C') return 2;
    else  return 3;
}
typedef long long ll;
int a, b, c;
const int maxb = 50000 + 100;
Complex x[4][maxb*4*2];
bool notprime[maxb];
void init() {
    memset(notprime, false, sizeof(notprime));
    notprime[0] = notprime[1] = true;
    for(int i = 2; i < maxb; i++) {
        if(!notprime[i]) {
            if(i > maxb/i) break;
            for(int j = i * i; j < maxb; j+=i) {
                notprime[j] = true;
            }
        }
    }
}

int main()
{
    // freopen("/Users/maoxiangsun/MyRepertory/acm/i.txt", "r", stdin);
    init();
    int cas = 0;
    while(~scanf("%d%d%d", &a, &b, &c) && a) {
        int len = 1, len0 = b + 1;
        while(len<4*len0) len<<=1;
        for(int k = 0; k < 4; k++) {
            x[k][0] = x[k][1] = Complex(0.0, 0.0);
            for(int i = 2; i < len0; i++) {
                if(notprime[i]) x[k][i] = Complex(1.0, 0.0);
                else x[k][i] = Complex(0.0, 0.0);
            }
            for(int i = len0; i < len; i++) {
                x[k][i] = Complex(0.0, 0.0);
            }
        }
        for(int i = 0; i < c; i++) {
            int p;char cc;
            scanf("%d%c", &p, &cc);
            x[idx(cc)][p].x = 0.0;
        }
        for(int i = 0; i < 4; i++) {
            fft(x[i], len, 1);
        }
        for(int i = 0; i < len; i++) {
            x[0][i] = x[0][i] * x[1][i] * x[2][i] * x[3][i];
        }
        fft(x[0], len, -1);
        for(int i = a; i <= b; i++) {
            printf("%lld\n", (ll)(x[0][i].x + 0.5));
        }
        printf("\n");
    }
    return 0;
}

/*
12 20 2 
4S 6H 
0 0 0
*/

猜你喜欢

转载自blog.csdn.net/sunmaoxiang/article/details/88560011