[POJ 1322] Chocolate (生成函数 或 概率dp)

题目描述

Problem Description
In 2100, ACM chocolate will be one of the favorite foods in the world.

“Green, orange, brown, red…”, colorful sugar-coated shell maybe is the most attractive feature of ACM chocolate. How many colors have you ever seen? Nowadays, it’s said that the ACM chooses from a palette of twenty-four colors to paint their delicious candy bits.

One day, Sandy played a game on a big package of ACM chocolates which contains five colors (green, orange, brown, red and yellow). Each time he took one chocolate from the package and placed it on the table. If there were two chocolates of the same color on the table, he ate both of them. He found a quite interesting thing that in most of the time there were always 2 or 3 chocolates on the table.

Now, here comes the problem, if there are C colors of ACM chocolates in the package (colors are distributed evenly), after N chocolates are taken from the package, what’s the probability that there is exactly M chocolates on the table? Would you please write a program to figure it out?
Input
The input file for this problem contains several test cases, one per line.

For each case, there are three non-negative integers: C (C <= 100), N and M (N, M <= 1000000).

The input is terminated by a line containing a single zero.
Output
The output should be one real number per line, shows the probability for each case, round to three decimal places.

题目大意

一个口袋中装有巧克力,巧克力的颜色有 c c c种。现从口袋中取出一个巧克力,若取出的巧克力与桌上某一已有巧克力颜色相同,则将两个巧克力都取走,否则将取出的巧克力放在桌上。设从口袋中取出每种颜色的巧克力的概率均等。求取出 N N N个巧克力后桌面上剩余 M M M个巧克力的概率。注意是无限输入,读入到 c = 0 c = 0 c=0为止

样例输入输出

Sample Input
5 100 2
0

Sample Output
0.625

解题思路

生成函数

根据题意,只有当 m < = n m <= n m<=n m < = c m <= c m<=c n − m n - m nm为偶数时才有解,下面我们只考虑有解的情况
首先我们可以根据题意将根据奇偶巧克力分成两类,其中,奇数个的有 m m m种颜色,那么我们可以选择其中一种分类的方式,将其写成生成函数的形式,即:
( e x − e − x 2 ) m × ( e x + e − x 2 ) c − m (\frac{e^x - e^{-x}}{2})^m \times (\frac{e^x + e^{-x}}{2})^{c - m} (2exex)m×(2ex+ex)cm
很容易知道最终的答案就为 a n × C m c × n ! c n \frac{a_n \times C_{m}^{c} \times n!}{c^n} cnan×Cmc×n!,其中 a n a_n an为上式展开后第n项的系数


最后的工作就是将上式展开,先二项式展开,得:
f ( x ) = 2 − c × ( ∑ i = 0 m ( − 1 ) i × C m i × e ( m − i ) x × e − i x ) × ( ∑ j = 0 c − m C c − m j × e j x × e − ( c − m − j ) x ) f(x) = 2 ^ {-c} \times ( \sum_{i = 0}^{m} (-1) ^ i \times C^{i}_{m} \times e^{(m - i)x} \times e^{-ix}) \times (\sum_{j = 0}^{c - m} C^{j}_{c - m} \times e^{jx} \times e^{-(c - m - j)x}) f(x)=2c×(i=0m(1)i×Cmi×e(mi)x×eix)×(j=0cmCcmj×ejx×e(cmj)x)
= 2 − c × ∑ i = 0 m ( − 1 ) i × C m i × e ( m − 2 i ) x × ∑ j = 0 c − m C c − m j × e ( 2 j + m − c ) x                     = 2 ^ {-c} \times \sum_{i= 0}^{m} (-1) ^ i \times C^{i}_{m} \times e^{(m - 2i)x} \times \sum_{j = 0}^{c - m} C^{j}_{c - m} \times e^{(2j+ m - c)x}\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ =2c×i=0m(1)i×Cmi×e(m2i)x×j=0cmCcmj×e(2j+mc)x                   
求一波卷积,得:
f ( x ) = 2 − c × ∑ i = 0 m ∑ j = 0 c − m ( − 1 ) i × C m i C c − m j × e ( 2 j − 2 i + 2 m − c ) x f(x)= 2 ^ {-c} \times \sum_{i= 0}^{m} \sum_{j = 0}^{c - m} (-1) ^ i \times C^{i}_{m} C^{j}_{c - m} \times e^{(2j - 2i + 2m - c)x} f(x)=2c×i=0mj=0cm(1)i×CmiCcmj×e(2j2i+2mc)x
再将 e ( 2 i + 2 j + 2 m − c ) x e^{(2i + 2j + 2m - c)x} e(2i+2j+2mc)x泰勒展开,得:
f ( x ) = 2 − c × ∑ i = 0 m ∑ j = 0 c − m ( − 1 ) i × C m i C c − m j × ∑ k = 0 ∞ ( ( 2 j − 2 i + 2 m − c ) x ) k k ! f(x)= 2 ^ {-c} \times \sum_{i= 0}^{m} \sum_{j = 0}^{c - m} (-1) ^ i \times C^{i}_{m} C^{j}_{c - m} \times \sum_{k = 0}^{\infty} \frac{((2j - 2i + 2m - c)x)^k}{k!} f(x)=2c×i=0mj=0cm(1)i×CmiCcmj×k=0k!((2j2i+2mc)x)k


再重复一遍,最后答案为:
a n × C m c × n ! c n \frac{a_n \times C_m^c \times n!}{c^n } cnan×Cmc×n!
代码里面没有 × 2 − c \times 2 ^ {-c} ×2c,故最后乘上;代码里把 × c − n \times c^{-n} ×cn甩到了每一次 a n a_n an的累加里,故没有最后计算;代码里再求 a n a_n an时,没有算 1 k ! \frac{1}{k!} k!1,故不用最后 × n ! \times n! ×n!
(写的有一点点小混乱,主要是Markdown写公式源码很丑,虽然写好后长得很好看。。。)

概率dp

前面扯了一堆堆的高 qiqiguaiguai 的玩意儿,相比之下,概率dp就简单易懂多了。。。
首先定义 d p [ i ] [ j ] dp[i][j] dp[i][j]表示桌子上已经摆出了 i i i颗巧克力,最终剩下了 j j j颗的概率,很容易给出转移方程式:
d p [ i ] [ j ] = d p [ i − 1 ] [ j + 1 ] × j + 1 c + d p [ i − 1 ] [ j − 1 ] × c − ( j − 1 ) c dp[i][j] = dp[i - 1][j + 1] \times \frac{j + 1}{c} + dp[i - 1][j - 1] \times \frac{c - (j - 1)}{c} dp[i][j]=dp[i1][j+1]×cj+1+dp[i1][j1]×cc(j1)
想要达到状态 ( i , j ) (i, j) (i,j)有两种情况:

  • 第一种,桌子上已有 j + 1 j + 1 j+1种不同颜色的巧克力的各一颗,那么想要使其加上第 i i i颗巧克力后只剩下 j j j颗巧克力就需要拿出一颗与原本已有的巧克力之一的颜色相同的巧克力,然后吃掉,此事件发生的概率为 j + 1 c \frac{j + 1}{c} cj+1
  • 第二种,桌子上只有 j − 1 j - 1 j1种不同颜色的巧克力,此时想要加上第 i i i颗巧克力后剩下 j j j颗就需要拿出一颗不同于桌上已有的巧克力的颜色的巧克力,此事件发生的概率为 c − ( j + 1 ) c \frac{c - (j + 1)}{c} cc(j+1)

综合以上两种情况即可得到最上面给出的状态转移方程式


做法是给出来了,可是我们发现此算法的时间复杂度为 O ( n m ) O(nm) O(nm);回头看一眼数据范围: C ≤ 100 C \leq 100 C100 N , M ≤ 1000000 N, M \leq 1000000 N,M1000000,然后就可以 自闭 去了。。。
接着一波很迷的操作就来了,玄学得贼有道理。。。
我们发现数据范围很大,时间复杂度很大,但是,输出数据的精度很小,所以 N N N越变越大,对答案的贡献越来越小,小到对答案产生不了影响,此时就可以停止计算了。这个时候, N N N大概在 500 500 500上下(保险一点取到 1000 1000 1000也不会 T T T

参考代码

生成函数

#include <cstdio>
#include <iostream>
using namespace std;

const int N = 100;
int c, n, m; double C[N + 5][N + 5], ans;

inline double qkpow (double x, int y) {
	double res = 1;
	for (; y; y >>= 1, x *= x)
		if (y & 1) res *= x; 
	return res;
}

inline void prepare () {
	C[0][0] = 1.0;
	for (int i = 1; i <= 100; ++ i) {
		C[i][0] = 1.0;
		for (int j = 1; j <= i; ++ j)
			C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
	}
}

int main () {
	prepare ();
	while (scanf ("%d", &c) && c) {
		scanf ("%d %d", &n, &m);
		if (m > c || m > n || (n - m) & 1) {
			printf ("0.000\n"); continue;
		}
		ans = 0.0;
		for (int i = 0; i <= m; ++ i)
			for (int j = 0; j <= c - m; ++ j)
				ans += (i & 1 ? -1 : 1) * C[m][i] * C[c - m][j] 
					   * qkpow ((2 * (m - i + j) - c) * 1.0 / c, n);
		ans = ans * C[c][m] / qkpow (2.0, c);
		printf ("%.3lf\n", ans);
	} 
	return 0;
}

概率dp
代码用的滚动数组,不滚动也可以

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;

const int N = 100;
int c, n, m; double dp[2][N + 5];

int main () {
	while (scanf ("%d", &c) && c) {
		scanf ("%d %d", &n, &m);
		
		if (m > c || m > n || (n - m) & 1) {
			printf ("0.000\n"); continue;
		}
		if (n >= 500) n = 500 - (n & 1);
		
		memset (dp, 0, sizeof dp);
		dp[0][0] = dp[1][1] = 1.0;
		for (int i = 2; i <= n; ++ i) {
			dp[i & 1][0] = dp[(i - 1) & 1][1] / c;
			dp[i & 1][c] = dp[(i - 1) & 1][c - 1] / c;
			
			for (int j = 1; j <= i && j < c; ++ j)
				dp[i & 1][j] = (j + 1.0) / c * dp[(i - 1) & 1][j + 1]
							 + (c - j + 1.0) / c * dp[(i - 1) & 1][j - 1];
		}
		printf ("%.3f\n", dp[n & 1][m]);
		//不晓得为嘛,用 %lf 输出它会炸(听取WA声一片...)
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值