1. 程式人生 > >UVa12298(生成函數的簡單應用+FFT)

UVa12298(生成函數的簡單應用+FFT)

further make 常數 opera output 調用 algorithm art there

I have a set of super poker cards, consisting of an in?nite number of cards. For each positive composite integer p, there are exactly four cards whose value is p: Spade(S), Heart(H), Club(C) and Diamond(D).

There are no cards of other values.

我有一副超級撲克包含無數張牌,如果數字是正合數的話,那就有四種牌:黑桃(S)、紅桃(H)、梅花(C)、方片(D)。

By “composite integer”, we mean integers that have more than 2 divisors. For example, 6 is a composite integer, since it has 4 divisors: 1, 2, 3, 6; 7 is not a composite number, since 7 only has 2 divisors: 1 and 7. Note that 1 is not composite (it has only 1 divisor).

比如6有4個因子(1,2,3,6)所以它是合數;7只有兩個(1,7)那就不是合數。

Given a positive integer n, how many ways can you pick up exactly one card from each suit (i.e. exactly one spade card, one heart card, one club card and one diamond card), so that the card values sum to n? For example, if n = 24, one way is 4S + 6H + 4C + 10D, shown below:

給定一個整數n,從四種花色各選一張牌,使得四張牌點數和為n,求解有多少種選法組合。比如n=24時,其中的一種選法可以為24=4+6+4+10,如下圖所示:

技術分享圖片


Unfortunately, some of the cards are lost, but this makes the problem more interesting. To further make the problem even more interesting (and challenging!), I’ll give you two other positive integers a and b, and you need to ?nd out all the answers for n = a, n = a + 1, ..., n = b.

不幸的是有的牌丟失了,樣例會給出哪些丟了。並且為了題目更加因吹斯聽,實際上不是給你n,而是給兩個數a、b,輸出所有n=a、n=a+1、n=a+2、……、n=b時的答案。


Input
The input contains at most 25 test cases. Each test case begins with 3 integers a, b and c, where c is the number of lost cards. The next line contains c strings, representing the lost cards. Each card is formatted as valueS, valueH, valueC or valueD, where value is a composite integer. No two lost cards are the same. The input is terminated by a = b = c = 0. There will be at most one test case where a = 1, b = 50,000 and c ≤ 10,000. For other test cases, 1 ≤ a ≤ b ≤ 100, 0 ≤ c ≤ 10.

輸入有多組數據,每組數據第一行三個整數a、b、c,a、b如上,c為丟失卡牌數;第二行給出c個具體的卡牌為丟失的。abc均為0時輸入結束。


Output
For each test case, print p integers, one in each line. Print a blank line after each test case.

輸出如題~記得空行


Sample Input
12 20 2

4S 6H

0 0 0
Sample Output
0

0

0

0

0

0

1

0

3

拜早年啦!年夜是刷題,還是邊看番邊刷題,還是帶親戚家小孩刷題啊?

口胡思路:

先以只有兩色四張牌的簡單題為例。假如第一個花色S有4和6兩個點數,則可以用一個多項式表示所有可選情況:x^4 + x^6;第二個花色H有8和10兩個點數,可用:x^8 + x^10表示。

我們將這兩式相乘得(x^4 + x^6)(x^8 + x^10) = x^12 + 2 * x^14 + x^16,這個結果代表的意義就是:和為12的組合有1種,和為14的組合有2種(4S+10H或6S+8H),和為16的有1種。

好,那麽對應到最開始的這道題目上來,就是稍微復雜了一點。對於每個花色,x的次冪為質數時系數為0,即多項式中只存在合數項。然後合數中也會有丟失的,其系數也置零。接下來四個多項式相乘即可,最終的多項式中x的次冪為n的項的系數,就是和為n的組合數。

事實上,這是離散數學中的一個小知識點,如題,是生成函數的一個簡單應用,在此處幫大家復習一下。

另外,兩多項式相乘的樸素做法顯然是O(n^2)的,用FFT或者NTT加速成nlogn嘍。不過快速傅裏葉變換和數論變換不是一兩句話可以口胡明白的,也不是本文想講的知識,姑且略去……

最後,如果自己手寫的FFT而沒用complex庫裏的復數運算的話,這題卡精度,要用long double。

諸君來看代碼吧:

  1 #include <cstdio>
  2 #include <cstring>
  3 #include <cmath>
  4 #include <algorithm>
  5 using namespace std;
  6 
  7 const int maxn = 50010;
  8 const long double PI = acos(-1.0);
  9 
 10 struct Complex {
 11     long double x, y;
 12     Complex(long double a = 0, long double b = 0):x(a), y(b){}
 13 };
 14 //手寫復數運算
 15 Complex operator + (Complex A, Complex B) { return Complex(A.x + B.x, A.y + B.y); }
 16 Complex operator - (Complex A, Complex B) { return Complex(A.x - B.x, A.y - B.y); }
 17 Complex operator * (Complex A, Complex B) { return Complex(A.x * B.x - A.y * B.y, A.x * B.y + A.y * B.x); }
 18 
 19 Complex n[maxn * 4], m[maxn * 4];//像線段樹一樣要開4倍
 20 int r[maxn * 4], l, limit, nl;
 21 /*---以上為FFT所需部分---*/
 22 
 23 int a, b, c;//abc含義見題面
 24 bool composite[maxn];//合數記錄
 25 bool lost[4][maxn];//卡牌丟失記錄
 26 
 27 void init(int n) {//求50000以內的合數
 28     int m = int(sqrt(n) + 0.5);
 29     for (int i = 2; i <= m; i++)
 30         if (!composite[i])
 31             for (int j = i * i; j <= n; j += i)
 32                 composite[j] = 1;
 33 }
 34 
 35 const char *ss = "SHCD";
 36 int idx(char c) {//巧得丟失字符位置
 37     return strchr(ss, c) - ss;
 38 }
 39 
 40 /*--以下為一個普通的FFT板子--*/
 41 void fft_init() {
 42     limit = 1, l = 0;
 43     while (limit < 2*(b + 1)) {
 44         limit <<= 1;
 45         l++;
 46     }
 47     
 48     for (int i = 0; i < limit; i++)
 49         r[i] = (r[i>>1] >> 1) | ((i&1) << (l-1));
 50 }
 51 
 52 void fft(Complex *A, int type) {
 53     for (int i = 0; i < limit; i++)
 54         if (i < r[i])
 55             swap(A[i], A[r[i]]);
 56 
 57     for (int mid = 1; mid < limit; mid <<= 1) {
 58         Complex Wn(cos(PI / mid), type * sin(PI / mid));
 59         for (int R = mid << 1, j = 0; j < limit; j += R) {
 60             Complex w(1, 0);
 61             for (int k = 0; k < mid; k++, w = w * Wn) {
 62                 Complex x = A[j+k], y = w * A[j+k+mid];
 63                 A[j+k] = x + y;
 64                 A[j+k+mid] = x - y;
 65             }
 66         }
 67     }
 68 }
 69 
 70 void Fourier(Complex *A, Complex *B) {
 71     fft(A, 1);
 72     fft(B, 1);  
 73     for (int i = 0; i < limit; i++)
 74         A[i] = A[i] * B[i];
 75     fft(A, -1);
 76     for (int i = 0; i < nl + b + 1; i++)
 77         A[i].x /= limit, A[i].y = 0.0;
 78 }
 79 /*---FFT結束---*/
 80 
 81 int main() {
 82     init(50000);//求5萬以內合數
 83     while (~scanf("%d%d%d", &a, &b, &c) && a) {
 84         memset(lost, false, sizeof(lost));//丟牌數組
 85         for (int i = 0; i < c; i++) {
 86             int temp; char ch;
 87             scanf("%d%c", &temp, &ch);
 88             lost[idx(ch)][temp] = 1;//這張牌丟了
 89         }
 90 
 91         n[0].x = 1.0, n[0].y = 0.0;//同下方m數組註釋
 92         nl = 1;
 93         fft_init();//這個東西反復調用沒意義就先拿出來了
 94         for (int i = 0; i < 4; i++) {
 95             //n是上一輪結束時的多項式結果,nl是多項式n的長度
 96             //答案只要求a到b的多項式系數,所以次冪為b以上可以置零
 97             for (int j = nl; j <= limit; j++)
 98                 n[j].x = n[j].y = 0.0;
 99             //m數組用來表示當前花色的多項式,所以先全置零
100             for (int j = 0; j <= limit; j++)
101                 m[j].x = m[j].y = 0.0;
102             
103             for (int j = 4; j <= b; j++) {
104                 if (composite[j] && !lost[i][j])
105                     m[j].x = 1.0, m[j].y = 0.0;
106                     //將這個合數的多項式系數設為復數(1 + 0*i),即實數的1
107             }
108             Fourier(n, m);//n和m這兩個多項式相乘
109             nl = b + 1;//b+1的原因是常數項也是一項
110         }
111     
112         for (int i = a; i <= b; i++)//x為實數項,即系數
113             printf("%lld\n", (long long)(n[i].x + 0.5));
114         puts("");       
115     }
116     return 0;
117 }

UVa12298(生成函數的簡單應用+FFT)