概率期望学习笔记
POJ3869
题意:两个人转左轮手枪,朝自己打,枪里保证至少有一个空的,你的对手上一轮活下来了,现在到你了,问重新转左轮和直接打,哪个概率高。
做法:考虑00,10,两种串,即可计算不转时,下一个为空的概率。重新转的概率,就是这个手枪里所有空的位置比所有的口的个数。注意串是循环的。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
#define pb push_back
typedef long long ll;
typedef double LD;
const int N = 2000000;
using namespace std;
char s[N];
int main() {scanf(" %s",s+1);int n = strlen(s+1),l=1,r=n,num0=0,num1=0;rep(ti,1,n) {if(s[r-1]=='0'&&s[r]=='0')++num0;if(s[r-1]=='1'&&s[r]=='0')++num1;++r; s[r]=s[l]; ++l;}if(num0*n>(num1+num0)*(num1+num0))puts("SHOOT");else if(num0*n<(num1+num0)*(num1+num0))puts("ROTATE");else puts("EQUAL");return 0;
}
POJ2794
题意:有9组牌,每组4张,两张牌的大小一样时可以消掉,每次只能消掉最上面的牌,然后一个在玩的时候如果有多种消法,就会随机选择其中一种,问他将所有牌都消掉的几率是多少。
做法:用一个9位5进制数表示当前的状态,直接记忆化搜索即可。一开始的写法T了。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
#define pb push_back
typedef long long ll;
typedef double LD;
const int N = 2000000;
using namespace std;
int a[20][20], M[266], num[9], A[9], f[22], ST, vis[N];
LD dp[N];
char s1[4];
double solve(int s) {if(vis[s]) return dp[s];vis[s]=1;int A[9],tmp=0;rep(i,0,8) A[i]=(s/f[i])%5;rep(i,1,8)rep(j,0,i-1) {if(A[i]&&A[j]&&a[i][A[i]]==a[j][A[j]]) {dp[s] += solve(s-f[i]-f[j]);++tmp;}}if(tmp)dp[s]/=(double)tmp;return dp[s];
}int main() {f[0]=1; rep(i,1,9)f[i]=f[i-1]*5;ST = f[9]-1;M['6']=0;M['7']=1;M['8']=2;M['9']=3;M['T']=4;M['J']=5;M['Q']=6;M['K']=7;M['A']=8;rep(i,0,8)rep(j,1,4) {scanf(" %s",s1);a[i][j] = M[s1[0]];}vis[0]=1; dp[0]=1.0;cout << solve(ST) << '\n';//TLE
// memset(dp,0,sizeof(dp));
// int x,y,t,z,tmp;LD tt;
// dp[ST]=1.0;
// per(s,ST,0) {
// z = 0;tmp=0;
// rep(i,0,8) {
// x = (s/f[i])%5;
// if(num[a[i][x]]==0)num[a[i][x]]=1,++tmp;
// z+=x;
// }
// memset(num,0,sizeof(num));
// if(tmp==9)continue;
// if(z&1)continue;
// tmp=0;
// rep(j,0,8) {x = (s/f[j])%5; if(x>0)++num[a[j][x]];}
// rep(j,0,8) if(num[j]>=2) tmp += (1*(num[j]*(num[j]-1))>>1);
// tt = (LD)dp[s]/tmp;
//
// rep(j,0,8) A[j]=(s/f[j])%5;
// rep(j,1,8)rep(k,0,j-1) {
// x=A[k], y=A[j];
// if(x!=0&&y!=0&&a[j][y]==a[k][x]){
// t = s; t-=(f[k]+f[j]);
// dp[t] += tt;
// }
// }
// }
// printf("%f\n",(double)dp[0]);return 0;
}
Codeforces1009E
题意:给定序列a,要求把长度n,划分成一些段,每一段的值形如a1,a2,..,求这些值和的期望。
做法:考虑每一个位置的期望。对于位置i,值为a1,只有它的前面恰好是一个分界线;值为a2,即他向前一个的位置恰好是个分界线;同理,可以列数位置i的值的期望为:
\(E_i = \frac{a_1}{2^{1}} + \frac{a_2}{2^{2}} + ... + \frac{a_{i-1}}{2^{i-1}} + \frac {a_i}{2^{i-1}}\)
\(E_1 = a_1\)
\(E_2 = \frac{a_1}{2^{1}} + \frac {a_2}{2^{1}}\)
\(E_3 = \frac{a_1}{2^{1}} + \frac{a_2}{2^{2}} + \frac {a_3}{2^{2}}\)
$E_4 = \frac{a_1}{2^{1}} + \frac{a_2}{2^{2}} + \frac {a_3}{2^{3}} + \frac {a_4}{2^{3}} $
直接求和即可。
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define per(i,a,b) for(int i=a;i>=b;--i)
#define pb push_back
typedef long long ll;
const int N = 1e6 + 7;
const ll mod = 998244353;
using namespace std;
int n;
ll a[N],ans=0,f[N];int main() {scanf("%d",&n);f[0]=1;rep(i,1,n)f[i]=(f[i-1]*2LL)%mod;rep(i,1,n) scanf("%I64d",&a[i]);rep(i,1,n) {ll t;if(i<n) t = ((a[i]*f[n-i])%mod + (a[i]*((f[n-i-1]*(n-i))%mod))%mod)%mod;else t = (a[i]*f[n-i])%mod;ans=(ans+t%mod)%mod;}printf("%I64d\n",ans);return 0;
}
Codeforces930B
题意:给定一个串,对他循环移位,通过首字母和一个后边的字母判断移了多少位,问成功的概率。
做法:对每种移位情况下,枚举每次去查哪个位置,对每种字母找到最好的位置。即使得尽可能多的情况下,这个位置的字母是不同的,使得成功的概率更高。设字母\(c_i\)的最优的情况后边有\(num_i\)个位置,字母唯一,\(c_i\)的个数为\(cnt(c_i)\),所以以这种字母开头,成功概率为\(\frac{num_i}{cnt(c_i)}\),把所有字母累加起来就是:\(\sum {\frac{num_i}{cnt(c_i)}·\frac {cnt(c_i)}{n}} = \frac {\sum {num_i}}{n}\)
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define per(i,a,b) for(int i=a;i>=b;--i)
#define pb push_back
typedef long long ll;
const int N = 5005;
using namespace std;
int n,ff[N],num[26];
char s[N<<1];
vector<int> v[26];
int main() {scanf(" %s",s+1);n = strlen(s+1);rep(i,1,n) s[i+n]=s[i];rep(i,1,n) v[s[i]-'a'].pb(i);rep(i,0,25)rep(x,1,n-1){int f=0;memset(num,0,sizeof(num));for(auto p: v[i]) ++num[s[p+x]-'a'];int tt = 0;rep(j,0,25)if(num[j]==1)++tt;ff[i]=max(ff[i],tt);}int tmp = 0;rep(i,0,25)tmp+=ff[i];printf("%.10f\n",1.0*tmp/(double)n);return 0;
}
Codeforces935D
题意:给定两个序列S1, S2,他们有一些位置的值是确定的,一些是不确定的,可以填入1~m中的数,问S1比S2字典序高的概率。
做法:从高位到低位递推,dp[i][0]表示前i个位置S1等于S2的概率,dp[i][1]表示前i个位置,S1大于S2的概率。分情况讨论,直接做即可。
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define per(i,a,b) for(int i=a;i>=b;--i)
#define pb push_back
typedef long long ll;
const int N = 1e5 + 7;
const int mod = 1e9 + 7;
using namespace std;
ll n,m,a[N],b[N],dp[N][2],invm,invmm,inv2;
ll q_pow(ll a,ll b) {a%=mod;ll ans = 1;while(b) {if(b&1) ans=(ans*a)%mod;a=(a*a)%mod;b>>=1;}return ans;
}
ll P(int i,int opt) {if(!opt) {if(a[i]&&b[i]&&a[i]==b[i]) return 1LL;if(a[i]&&b[i]&&a[i]!=b[i]) return 0LL;if(!a[i]&&b[i]) return invm%mod;if(a[i]&&!b[i]) return invm%mod;if(!a[i]&&!b[i]) return invm%mod;}else {if(a[i]&&b[i]&&a[i]>b[i]) return 1LL;if(a[i]&&b[i]&&a[i]<=b[i]) return 0LL;if(!a[i]&&b[i]) return ((m-b[i])%mod*invm)%mod;if(a[i]&&!b[i]) return ((a[i]-1LL)%mod*invm)%mod;if(!a[i]&&!b[i]) return ((((((m-1LL)%mod)*m)%mod*inv2)%mod)*invmm)%mod;}
}int main() {scanf("%I64d%I64d",&n,&m);inv2 = q_pow(2LL,mod-2);invm = q_pow(m,mod-2);invmm = (invm*invm)%mod;rep(i,1,n)scanf("%I64d",&a[n-i+1]);rep(i,1,n)scanf("%I64d",&b[n-i+1]);dp[1][0]=P(1,0), dp[1][1]=P(1,1);rep(i,2,n) {dp[i][0] = (P(i,0)*dp[i-1][0])%mod;dp[i][1] = (P(i,1) + (P(i,0)*dp[i-1][1])%mod)%mod;}(dp[n][1]+=mod)%=mod;printf("%I64d\n",dp[n][1]);return 0;
}
Codeforces280C
题意:给定一棵树,每次操作可以删除一颗子树。问期望的操作次数。
题解:考虑每个点对答案的贡献,点i最终一定会被删除,如果删除它的时候,是在删除它的祖先,则他对答案无贡献,如果是通过自己删的,则对答案有贡献。能删除它的祖先共有:这个点的深度-1 个点。所以点u的期望就是\(\frac{1}{deep[u]}\),最后累加起来。一开始,推出了深度是1的树的公式,然后推广了一下,写的自底向上算。。结果gg,这题思路真的有点神。
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define per(i,a,b) for(int i=a;i>=b;--i)
typedef long long ll;
const int N = 1e5 +7;
using namespace std;
struct edge{int e,nxt;}E[N<<1];
int cc,h[N];
void add(int u,int v) {E[cc].e=v;E[cc].nxt=h[u];h[u]=cc;++cc;}
int n,dep[N];
double ans = 0;
void dfs(int u,int pre) {ans += 1.0/dep[u];for(int i=h[u];~i;i=E[i].nxt) if(E[i].e!=pre) {dep[E[i].e] = dep[u] + 1;dfs(E[i].e,u);}
}
int main() {scanf("%d",&n);rep(i,1,n) h[i] = -1;rep(i,1,n-1) {int x,y;scanf("%d%d",&x,&y);add(x,y), add(y,x);}dep[1] = 1; dfs(1,0);printf("%.12f\n",ans);return 0;
}
Codeforces452C
题意:把n副一样的牌混在一起,每副牌包含m张,从其中选n张,问取两次牌相同的概率。
做法:考虑一种牌x的贡献,枚举x的个数k,现在先从n*m张牌中挑n张,其中有k张x,然后再考虑从中两次选出x的概率,可以列出式子:\[\sum_{k=1}^{min(m,n)} \frac {C^k_m·C_{nm-m}^{n-k}·k^2}{C_{nm}^n·n}\]
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define per(i,a,b) for(int i=a;i>=b;--i)
typedef long long ll;
const int N = 1e5 +7;
using namespace std;
int n,m;
int main() {scanf("%d%d",&n,&m);double Cnmn = 1.0, Cmx = m, Cnm_x = 1.0, ans = 0;rep(i,1,n) Cnmn = Cnmn * (n*m - i + 1)/i;rep(i,1,n-1) Cnm_x = Cnm_x * ((n-1.0)*m - i + 1)/i;rep(i,1,min(n,m)) {ans += Cmx*Cnm_x*i*i/Cnmn/n;Cmx = Cmx * (m - (i+1) + 1.0)/(i+1);Cnm_x = Cnm_x * (n-(i+1)+1)/((n-1.0)*m - (n-(i+1)+1) + 1);}printf("%.10f\n",ans);return 0;
}
Codeforces261B
\(f[i][j][k]\)表示前i个元素取j个,构成体积k的方法数,枚举最后放不进去的元素是哪个,每种情况对答案的贡献,就是\(\sum {f[n][j][k]·j!·(n-j-1)!}\),计算f[i][j][k]的时候记得不要放x,最后的答案除\(n!\),和背包一样,\(i\) 这一维可以去掉,j和k都相当于一种体积。。。智商逐渐消失。。
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define per(i,a,b) for(int i=a;i>=b;--i)
#define pb push_back
typedef long long ll;
const int N = 52;
using namespace std;
int n,a[N],p,sum;
double f[N][N], ans, fc[N]; // (前i个元素)取j个,构成体积k的方法数int main() {scanf("%d",&n);rep(i,1,n) scanf("%d",&a[i]),sum+=a[i];scanf("%d",&p);fc[0] = 1.0;rep(i,1,n) fc[i]=fc[i-1]*i;if(sum <= p) {printf("%.10f\n",(double)n);}else {rep(x,1,n) { // 枚举那个恰好没被使用的元素memset(f,0,sizeof(f));rep(i,0,n) f[0][0] = 1;rep(i,1,n) {per(k,p,a[i]) per(j,i,1) {if(i!=x)f[j][k] += f[j-1][k-a[i]];}}rep(i,0,n-1) rep(k,max(p-a[x]+1,0),p) {ans += f[i][k]*fc[i]*fc[n-i-1]*i;}}ans /= fc[n];printf("%.10f\n",ans);}return 0;
}