「题解」「POJ1322」Chocolate
题目
原题目
简易题意
包裹里有无限个分布均匀且刚好 \(c\) 种颜色的巧克力,现在要依次拿 \(n\) 个出来放到桌子上。每次如果桌子上面有两种相同颜色的巧克力就会把这两个巧克力给吃掉,求最后桌子上面还有 \(m\) 个巧克力的概率。
思路分析
首先,我们考虑使用概率 \(\text{DP}\) ,似乎是可做的。
但是,由于本题数据范围限制:\(N,M\le 1000000\) ,如果再从 \(DP\) 的角度思考,时间复杂度似乎有些出乎所料了,考虑用其他办法解决此题。
本人经验之谈:
没有入手点——考虑分析题目特性,进而找到对应方法解决此题。
考虑本题的一些特性:
- \(m\le c\) ,因为如果糖果超出了 \(c\) ,就必定有重复的颜色出现,就被吃掉了。
- \(m\le n\) ,显然吧...
- \(2\mid (n-m)\) ,因为被吃掉当有两块重复的巧克力。
- 设颜色 \(i\) 的糖果被取出来的次数为 \(t_i\) 。那么,如果 \(2\mid t_i\) ,则颜色 \(i\) 最终不会生下来;反之,颜色 \(i\) 最终会剩下来。
- 方案数共 \(c^n\) 种,显然...
考虑从这些特性入手。
对于前三个特征,似乎只是我们的骗分策略...当然,对于特征一,可以用来优化 \(DP\) ,但是还需卡常才能过。
针对特征四,似乎可以进行发散:
对于我们取出来的 \(n\) 个球,有 \(m\) 个颜色取了奇数个,剩余 \(c-m\) 个颜色取了偶数个。
有过生成函数的经验,我们似乎可以很容易列出生成函数。
对于奇数颜色
对于偶数颜色
如果我们按通常的方法,将他们卷起来,你就会发现,即使你 \(WA\) 穿了,这道题也做不对。
为什么?因为这道题求的是概率,但如果你就这样卷起来,其顺序是不对的。
举个栗子,取颜色的顺序为 \(112\) 与颜色顺序 \(121\) 。
这两种剩下的结果都是 \(2\) ,但是它们属于不同的方案。
我们就要先想办法将他们变成相同的方案,最后再乘以其排列 \(n!\) 。
引入指数型母函数的性质:
指数型母函数的标志函数一般为 \(\frac{x^0}{0!}+\frac{x^1}{1}+\frac{x^2}{2!}+...\) ,对于 \(\frac{x^i}{i!}\) 表示在一个方案中某个元素出现了 \(i\) 次,而不同位置的该种元素本质不同,所以在记方案数时只算作一种,所以最后结果应处以 \(i!\) 。
根据指数型母函数的定义,对于奇数的颜色,其生成函数应该为
同理,偶数的颜色为
那么,答案的生成函数即为
注:从此处开始,我们就开始一直处理其系数问题,而最后的排列需要在此基础上乘以 \(n!\) 。
考虑如何化简 \(H(x)\) ,想办法简化 \(F、G\) 。
分析其一般形式,令 \(A(x)=\sum_{i=0}^\infty \frac{x^i}{i!}\) 。
有 \(e^x=A(x)\) ,下面进行证明:
设 \(B(x)=e^x\) ,已知 \(e\) 的特性:
\(f(x)=e^x\) 存在任意阶导数,且 \(f^{(n)}(x)=f(x)\) 。
那么,我们考虑将 \(B(x)\) 在 \(x_0=0\) 处进行泰勒展开,那么就有
所以,就有 $ek=\sum_{i=0}\infty \frac{x^i}{i!} $
那么,\(e^{-k}=\sum_{i=0}^\infty \frac{x^i}{i!}(-1)^i\)
有了这个工具,我们就可以很好地表示 \(F、G\) ,显然
那么就有
其中,我们将 \((1)\) 二项式展开之后得到 \((2)\) ,然后再将其化简得到后面的算式。
出现了 \(x^k\) ,我们将它换个写法:
那么,\(x^n\) 的系数 \(a_n\) 的值就是
由于我们处理的是系数,那么答案就是
而 \(a_n\) 中有一个 \(\frac{1}{n!}\) ,所以我们将 \(a_n\) 分母中的 \(n!\) 去掉,答案就变为
其中
代码
#define rep(i,__l,__r) for(int i=__l,i##_end_=__r;i<=i##_end_;++i)
#define fep(i,__l,__r) for(int i=__l,i##_end_=__r;i>=i##_end_;--i)
const int MAXN=100;
inline double qkpow(double a,int n){
// printf("Now qkpow : a == %.3lf, n == %d\n",a,n);
double ret=1;
for(;n>0;n>>=1){
if(n&1)ret*=a;
a*=a;
// printf("Now n == %d, a == %.3lf, ret == %.3lf\n",n,a,ret);
}
// printf("ans == %.3lf\n",ret);
return ret;
}
double C[MAXN+5][MAXN+5];
inline void init(){
C[0][0]=1;
rep(i,1,MAXN){
C[i][0]=1;
rep(j,1,i){
C[i][j]=C[i-1][j-1]+C[i-1][j];
}
}
}
signed main(){
#ifdef FILEOI
freopen("file.in","r",stdin);
freopen("file.out","w",stdout);
#endif
int c,n,m;
double a;
init();
while(c=qread()){
qread(n,m);a=0;
if(m>c || m>n || (n-m)&1){
puts("0.000");
continue;
}
rep(i,0,m)rep(j,0,c-m){
a+=(i&1?-1:1)*C[m][i]*C[c-m][j]*qkpow((2*m-2*i+2*j-c)*1.0/c,n);
//此处的快速幂的底数 /c 的原因是为了防止爆 double , 而我们本来就要除以 c^n , 因而我们就在 qkpow 里面处理掉了
}
a=a*C[c][m]/qkpow(2.0,c);
printf("%.3lf\n",a);
}
return 0;
}
练习题
\(\text{[HDU2056]}\) "红色病毒"问题
#include<cstdio>
#include<vector>
#include<utility>
#include<algorithm>
using namespace std;
#define rep(i,__l,__r) for(int i=__l,i##_end_=__r;i<=i##_end_;++i)
#define fep(i,__l,__r) for(int i=__l,i##_end_=__r;i>=i##_end_;--i)
#define writc(a,b) fwrit(a),putchar(b)
#define mp(a,b) make_pair(a,b)
#define ft first
#define sd second
#define LL long long
#define ull unsigned long long
#define uint unsigned int
#define pii pair<int,int>
#define Endl putchar('\n')
// #define FILEOI
// #define int long long
#ifdef FILEOI
#define MAXBUFFERSIZE 500000
inline char fgetc(){
static char buf[MAXBUFFERSIZE+5],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,MAXBUFFERSIZE,stdin),p1==p2)?EOF:*p1++;
}
#undef MAXBUFFERSIZE
#define cg (c=fgetc())
#else
#define cg (c=getchar())
#endif
template<class T>inline void qread(T& x){
char c;bool f=0;
while(cg<'0'||'9'<c)f|=(c=='-');
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
if(f)x=-x;
}
inline int qread(){
int x=0;char c;bool f=0;
while(cg<'0'||'9'<c)f|=(c=='-');
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
return f?-x:x;
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
inline int gcd(const int a,const int b){return b?gcd(b,a%b):a;}
inline void getInv(int inv[],const int lim,const int MOD){
inv[0]=inv[1]=1;for(int i=2;i<=lim;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
}
template<class T>void fwrit(const T x){
if(x<0)return (void)(putchar('-'),fwrit(-x));
if(x>9)fwrit(x/10);putchar(x%10^48);
}
inline LL mulMod(const LL a,const LL b,const LL mod){//long long multiplie_mod
return ((a*b-(LL)((long double)a/mod*b+1e-8)*mod)%mod+mod)%mod;
}
const int MOD=100;
ull N;int T;
inline int qkpow(int a,ull n){
int ret=1;
for(;n>0;n>>=1,(a*=a)%=MOD)if(n&1)(ret*=a)%=MOD;
return ret;
}
signed main(){
#ifdef FILEOI
freopen("file.in","r",stdin);
freopen("file.out","w",stdout);
#endif
int cas;
while((T=qread())^(cas=0)){
while(T--){
qread(N);
printf("Case %d: ",++cas);
writc((qkpow(4,N-1)+qkpow(2,N-1))%MOD,'\n');
}
Endl;
}
return 0;
}