組合數學主要研究計數問題。比如,從n 個人中選兩個人有多少種方法?圓周上有n個點,兩兩相連之後最多能把圓面分成多少部分?如圖2-16 所示。有一個金字塔,從塔頂開始每一層分別有1 x 1 , 2x2, ..., nXn 個小立方體,問一共有多少個小立方體?
很多問題的答案都可以寫成n 的簡單多項式。比如上述第一個問題的答案是n(n一1)/2 ,也就是(n^2 -n)/2 ; 第二個問題的答案是(n^4-6n^3 +23n2一18n+24)/24 ; 第三個問題的答案是n(n+ 1 )(2n+ 1 )/6 ,即(2n^3 +2n^2+n)/6 。由於上述3 個多項式是計數問題的答案,因此當n 取任意正整數時,這些多項式的值都是整數。當然,對於其他多項式,這個性質並不一定成立。給定一個形如P/D (其中P 是n 的整係數多項式, D 是正整數)的多項式,判斷它是否在所有正整數處取到整數值。
【分析】
本題實際上是判斷一個整係數多項式P 的值是否總是正整數D 的倍數。一個容易想到的方法是,隨機代入很多整數計算P/D , 如果全都是整數,那麼很有可能是"Always aninteger" ;如果有的不是整數,那麼答案必然是"Not always an integer" 。這個方法看起來有些投機取巧,但效果非常不錯。事實上,不需要隨機代入,只需要
把n=l , 2, 3, ..., k-t: l 全試一遍就可以了,其中k 是多項式中最高項的次數。爲什麼可以這樣做呢?讓我們從k 較小的情況開始研究。
- 當k=0 時, P 里根本就沒有n 個變量,所以只需代入P(l)計算即可。
- 當k= l 時, P 是n 的一次多項式,設爲an+b , 則P(n+l)-P(n )=a。如果把P(n)看成一個數列的第n J頁,則{P(n)} 是一個首項爲P(l) , 公差爲整數。的等差數列,因此只要首項和公差均爲D 的倍數,整個數列的所有項都會是D 的倍數。因此只需驗證P(l)和P(2) 。
- 當k=2 時, P 是n 的二次多項式,設爲αn2+bn+c , 則P(n+1)-P(n )=2an+α+b 。注意到這個2an+α+b 是n 的一次多項式,根據剛纔的結論,只要n=1 和n=2 時它都是D 的倍數,對於所有正整數n , 它都將是D 的倍數。這樣,相鄰兩項的差爲D 的倍數,再加上首項也爲D 的倍數,則P(n)將總是D 的倍數。整理一下,只要P(1), P(2)-P(1), P(3)-P(2)都是D 的倍數即可。這等價於驗證P(1) , P(2)和P(3) 。
看到這裏,結論己經不難猜到了。對於k 次多項式P(時,相鄰兩項之差P(n+1)-P(n)是關於n 的k- l 次多項式,根據數學歸納法,命題得證。順便說一句,數列dP(n)=P(n+ l)-P(n)稱爲P(n) 的差分數列(difference series) 。而差分數列的差分數列爲二階差分數歹ú cfp(時,依此類推。這樣, k=2 的證明可以用圖2-17 說明。二次多項式P(2) P(3) P(4) P(5) '" / '" / "' ./ -次多項式dP(2) dP(3) dP(4)
'" / '" / 常數d'-P(2) d'-P(3)
如果P(1), P(2), P(3)都是D 的倍數,意味着P(1), dP(1)和cfp(1)都是D 的倍數。由於第二行(即所有的cfP(n)) 是常數,所以整個第三行都是D 的倍數:根據差分的定義,可以推導出整個第二行都是D 的倍數,再進一步得到第一行也都是D 的倍數。
#include <iostream>
#include <string>
#include <cctype>
#include <vector>
#include <cstdlib>
using namespace std;
struct Polynomial{
vector<int> a, p; // 第i項爲a[i] * n^p[i]
void perse(string s){ // 解析多項式,多取一個)減少取數時的越界判斷
for(int i = 0, len = s.length(); i < len-1;){ // 每次循環體解析一個a*n^p
int sign = 1;
if(s[i] == '-'){
sign = -1;
i++;
}
if(s[i] == '+') i++;
int v = 0;
while(isdigit(s[i]))
v = v*10 + s[i++]-'0';
if(v == 0) v = 1; //無係數,按1處理
v *= sign;
a.push_back(v);
v = 1; //無指數默認爲1
if(s[++i] == '^'){
i++;
v = 0; // 有指數
if(s[i] == '-' ){ // 有指數項
sign = -1;
i++;
}
while(isdigit(s[i]))
v = v*10 + s[i++] - '0';
}
p.push_back(v);
}
}
int mod(int x,int MOD){
int ans = 0;
for(int i = 0; i < a.size(); i++){
int m = a[i];
for(int j = 0; j < p[i]; j++)
m = ((long long)m * x) % MOD; // 注意避免溢出
ans = ((long long)ans + m) % MOD; // 加法也可能會溢出!
}
return ans;
}
};
bool check(string s){
Polynomial p;
int loc = s.find('/');
p.perse(s.substr(1, loc-1)); //多取一個)防止越界判斷
int D = atoi(s.substr(loc+1).c_str());
for(int i = 1; i <= p.p[0]+1; i++)
if(p.mod(i,D) != 0) return false;
return true;
}
int main(int argc, char** argv) {
string s;
int kase = 0;
while(cin>> s && s[0] != '.'){
if(check(s)) printf("Case %d: Always an integer\n", ++kase);
else printf("Case %d: Not always an integer\n", ++kase);
}
return 0;
}