组合数学主要研究计数问题。比如,从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;
}