【HDU-2048】解题报告(递推,DP,数论,错排)
原始题目
神、上帝以及老天爷
- Time Limit: 2000/1000 MS (Java/Others)
- Memory Limit: 65536/32768 K (Java/Others)
- Total Submission(s): 50425
- Accepted Submission(s): 20379
Problem Description
HDU 2006'10 ACM contest的颁奖晚会隆重开始了! 为了活跃气氛,组织者举行了一个别开生面、奖品丰厚的抽奖活动,这个活动的具体要求是这样的:
首先,所有参加晚会的人员都将一张写有自己名字的字条放入抽奖箱中; 然后,待所有字条加入完毕,每人从箱中取一个字条; 最后,如果取得的字条上写的就是自己的名字,那么“恭喜你,中奖了!” 大家可以想象一下当时的气氛之热烈,毕竟中奖者的奖品是大家梦寐以求的Twins签名照呀!不过,正如所有试图设计的喜剧往往以悲剧结尾,这次抽奖活动最后竟然没有一个人中奖!
我的神、上帝以及老天爷呀,怎么会这样呢?
不过,先不要激动,现在问题来了,你能计算一下发生这种情况的概率吗?
不会算?难道你也想以悲剧结尾?!
Input
输入数据的第一行是一个整数C,表示测试实例的个数,然后是C 行数据,每行包含一个整数\(n(1<n≤20)\),表示参加抽奖的人数。
Output
对于每个测试实例,请输出发生这种情况的百分比,每个实例的输出占一行, 结果保留两位小数(四舍五入),具体格式请参照sample output。
Sample Input
1
2
Sample Output
50.00%
Author
lcy
Source
递推求解专题练习(For Beginner)
Recommend
lcy
题目大意
如题 # 解题思路
DP
手动写一下如何安排参加人员都不中奖就可以发现规律。\(D[i]\)表示i个人符合要求的方案数目
比如计算dp[4]时,将1的纸条分给2或3或4,这三种情况下,可以变为解决子问题,比如将1的纸条分给2,之后将2、3、4的纸条分给1、3、4,与dp[3]接近,但是1位置不受限制,即1位置可以放2的纸条,这样又变为dp[2]。
故得到状态转移方程。
\[ D[n]=(n-1)(D[n-1]+D[n-2]) \]
最后除以总情况数(排列),计算百分数即可。
错排公式
其实本题是一个错排公式题目,我们上面推导出来的$ D[i]=(i-1)(D[i-1]+D[i-2]) $,实际上就是错排公式的一个递推关系。
又因为\(D[1]=0,D[2]=1\) 继续推导 \[ \begin{align} D[n] & =(n-1)(D[n-1]+D[n-2]) \\ D[n] - n D[n-1] & = (-1) (D[n-1] - (n-1) D[n-2])\\ D[n] - n D[n-1] & = (-1)^{n-2} (D[2] - 2 D[1]) \\ D[n] & = n D[n-1] + (-1)^{n} \end{align} \] 如此我们得到了递推关系式,那么求通项公式可以用下面几种方法:
可以用母函数方法求出通项公式(还不会,待学了母函数填坑)
可以用容斥原理
最后得到错排公式为
\[ D[n] = n!( \frac{1}{0!} - \frac{1}{1!} + \frac{1}{2!} - \cdots (-1)^{n} \frac{1}{n!} ) \]
再补充
简化公式
\[ D(n) = [ \frac{n!}{e} + 0.5 ] \]
证明相见百度,e是自然对数的底,[x]为x的整数部分。
解题代码
DP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43#include <cstdio>
#include <cstring>
#include <iostream>
#include <set>
#include <algorithm>
#include <queue>
#include <map>
#include <string>
#define eps 1e-8
#define INF 0x3f3f3f3f
#define rep(i,a,n) for(int i=a;i<n;i++)
#define per(i,a,n) for(int i=n-1;i>=a;i--)
using namespace std;
const int maxn=1e5+5;
typedef long long ll;
ll dp[maxn],A[maxn];
void solve(){
memset(dp,0,sizeof(dp));
dp[1]=0;
dp[2]=1;
A[1]=1;
A[2]=2;
for(ll i=3;i<=20;i++){
dp[i]=(i-1)*(dp[i-1]+dp[i-2]);
A[i]=A[i-1]*i;
// cout<<dp[i]<<" "<<A[i]<<endl;
}
}
using namespace std;
int main(){
int t;
solve();
while(cin>>t){
rep(i,0,t){
ll a;
cin>>a;
double ans=(double)dp[a]/(double)A[a];
// cout<<"dp[i]"<<dp[a]<<" "<<"A[i]"<<A[a]<<endl;
printf("%.2lf%%\n",ans*100);
}
}
}错排简化公式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <iomanip>
#include <set>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <sstream>
#include <bits/stdc++.h>
#define rep(i,a,n) for(int i=a;i<n;++i)
#define per(i,a,n) for(int i=n-1;i>=a;--i);
#define ms(x,a) memset((x),a,sizeof((x)))
#define INF 0x3f3f3f3f
#define eps 1e-8
#define fi first
#define e 2.718281828459045235360287471352662497757247093699959574966967627724076630353
#define se second
#define mp make_pair
#define pb push_back
#define np next_permutation
#define gapline cout<<"##======================##"<<endl
using namespace std;
const int maxn=1e5+5;
const int maxl=26;
const int maxm=1e5+5;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> vi;
typedef pair<int,int> pii;
ll A[maxl];
void solve(){
A[0]=1;
A[1]=1;
rep(i,2,maxl) A[i]=A[i-1]*i;
}
int n;
int main(){
ios::sync_with_stdio(false);
solve();
ll t;
cin>>t;
while(t--){
cin>>n;
double ans=floor((double)A[n]/e+0.5);
cout<<fixed<<setprecision(2)<<ans*100/A[n]<<"%"<<endl;
}
}
收获与反思
注意分析递推公式。手写前几项找规律也可以。
错排公式推导学习
挖坑母函数
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!