一些比较诡异的关于概率的DP
T1 IMT
呃,可以KMP求出Fail的位置。
但是懒得那么做..直接暴力
O(nlen2)
匹配求出矩阵。
然后矩阵乘或者直接求都可以了。
代码
这题调很久的一个严重原因就是不会输出‘%’
..要用两个‘%%’或是‘\%’才行。
#include<bits/stdc++.h>
using namespace std;
#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define DB double
#define chkmin(a,b) a=min(a,b)
#define chkmax(a,b) a=max(a,b)
#define EPS 1e-6
#define M 14
int n,m,len;
char C[34],c[M];
string S,P,Tmp;
DB A[34],F[2][M],To[M][M];
int main(){
while(scanf("%d %d",&n,&m)!=EOF){
if(!n || !m)break;
REP(i,0,n){
scanf("%s %lf",&c,&A[i]);
C[i]=c[0];
}
scanf("%s",c);
S="";
len=strlen(c);
REP(i,0,len)S+=c[i];
REP(i,0,M) REP(j,0,M) To[i][j]=0;
P="";
REP(i,0,len){
REP(k,0,n){
Tmp=P+C[k];
DREP(j,i+1,-1){
if(j==0 || Tmp.substr(i-j+1,j)==S.substr(0,j)){
To[i][j]+=A[k];
break;
}
}
}
P+=S[i];
}
bool f=0;
DB Ans=0;
REP(k,1,M)F[0][k]=0;
F[0][0]=1;
while(m--){
REP(k,0,M)F[!f][k]=0;
REP(j,0,len) REP(k,0,len+1)
F[!f][k]+=F[f][j]*To[j][k];
f^=1;
Ans+=F[f][len];
}
printf("%.2lf%%\n",Ans*100.0);
}
return 0;
}
T2 R
给树上的每条边随机大小然后求直径小于S的树的概率。。
树形DP,合并子树的转移方法。
代码
#include<bits/stdc++.h>
using namespace std;
#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define DB double
#define chkmin(a,b) a=min(a,b)
#define chkmax(a,b) a=max(a,b)
#define M 84
#define Mm 544
int T,n,L,S;
int Next[M<<1],V[M<<1],Head[M],tot;
void Add_Edge(int u,int v){
Next[++tot]=Head[u],V[Head[u]=tot]=v;
}
#define LREP(i,A) for(int i=Head[A];i;i=Next[i])
DB F[M][Mm],Tmp[Mm],Val,Sum1[Mm],Sum2[Mm];
void DFS(int A,int f){
int B,Son=0;
F[A][0]=1;
LREP(i,A)if((B=V[i])!=f){
DFS(B,A);
memset(Tmp,0,sizeof(Tmp));
REP(j,0,L+1) REP(k,0,S-j+1)
Tmp[k+j]+=Val*F[B][k];
Sum1[0]=F[A][0];
REP(j,1,S+1)Sum1[j]=Sum1[j-1]+F[A][j];
Sum2[0]=Tmp[0];
REP(j,1,S+1)Sum2[j]=Sum2[j-1]+Tmp[j];
REP(j,0,S+1){
int ms=min(j,S-j);
DB t=F[A][j]*Tmp[j];
F[A][j]=F[A][j]*Sum2[ms]+Sum1[ms]*Tmp[j];
if((j<<1)<=S)F[A][j]-=t;
}
}
}
int main(){
scanf("%d",&T);
REP(Case,1,T+1){
scanf("%d %d %d",&n,&L,&S);
memset(Head,tot=0,sizeof(Head));
memset(F,0,sizeof(F));
REP(i,1,n){
int u,v;
scanf("%d %d",&u,&v);
Add_Edge(u,v);
Add_Edge(v,u);
}
Val=1.0/(L+1);
DFS(1,0);
DB Ans=0;
REP(i,0,S+1)Ans+=F[1][i];
printf("Case %d: %.6lf\n",Case,Ans);
}
return 0;
}
T3 LPAD
如果
n≤105
的话是一道大水题、
然而
n≤109
,
其实还是一道大水题。
因为当n慢慢变大的时候概率的改变就可以忽略不计了。
也就是说当概率改变量减小到EPS以下时直接输出即可。
代码
#include<bits/stdc++.h>
using namespace std;
#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define chkmax(a,b) a=max(a,b)
#define chkmin(a,b) a=min(a,b)
#define DB double
#define M 1000004
#define EPS 1e-13
int n,m;
DB F[M];
int main(){
while(scanf("%d %d",&m,&n)!=EOF){
memset(F,0,sizeof(F));
F[0]=1;F[1]=-1;
DB Val=1.0/m;
int i=0;
while(i<=n){
if(i){
F[i]+=F[i-1];
if(fabs(F[i]-F[i-1])<EPS)break;
}
if(i+1<M) F[i+1]+=F[i]*Val;
if(i+m+1<M) F[i+m+1]-=F[i]*Val;
i++;
}
printf("%.5lf\n",F[i-1]);
}
return 0;
}
T4 W
求图中长度为d的路径不经过某个点的概率。
原本应该直接写的,然而看错时间范围(15s)。
于是默默写了一遍矩阵乘….复杂度
O(n4logd)
代码
#include<bits/stdc++.h>
using namespace std;
#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define chkmax(a,b) a=max(a,b)
#define chkmin(a,b) a=min(a,b)
#define DB double
#define M 54
int Case,n,m,d;
bool P[M][M];
struct Mat{
DB V[M][M];
void Clear(){memset(V,0,sizeof(V));}
Mat friend operator *(Mat A,Mat B){
Mat C;C.Clear();
REP(i,1,n+1) REP(j,1,n+1) REP(k,1,n+1) C.V[i][j]+=A.V[i][k]*B.V[k][j];
return C;
}
}K,Res;
void Solve(int x){
K.Clear();Res.Clear();
REP(i,1,n+1)if(i!=x)Res.V[i][i]=1.0/n;
REP(i,1,n+1)if(i!=x){
int Tmp=0;
REP(j,1,n+1) Tmp+=P[i][j];
REP(j,1,n+1) if(j!=x && P[i][j]) K.V[i][j]=1.0/Tmp;
}
int p=d;
while(p){
if(p&1)Res=Res*K;
K=K*K;p>>=1;
}
DB Ans=0;
REP(i,1,n+1) REP(j,1,n+1)Ans+=Res.V[i][j];
printf("%.10lf\n",Ans);
}
int main(){
scanf("%d",&Case);
while(Case--){
scanf("%d %d %d",&n,&m,&d);
memset(P,0,sizeof(P));
REP(i,0,m){
int u,v;
scanf("%d %d",&u,&v);
P[u][v]=P[v][u]=1;
}
REP(i,1,n+1)Solve(i);
}
return 0;
}
T5 BJ
先考虑庄家,此时玩家已经拿完牌。
那么庄家只能继续拿牌直到他的点数≥玩家的点数。
定义这个概率为
Gi,j
,即庄家
i
点玩家
那么玩家的概率反着求一下即可。
代码
#include<bits/stdc++.h>
using namespace std;
#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define chkmax(a,b) a=max(a,b)
#define chkmin(a,b) a=min(a,b)
#define DB double
#define M 14
#define N 34
int Test;
DB F[M];
char C[M];
map<char,int>V;
DB G[N][N],DP[N][N];
int main(){
REP(i,1,10)F[i]=1.0/13;
F[10]=4.0/13;
V['A']=1;
V['2']=2,V['3']=3,V['4']=4,V['5']=5;
V['6']=6,V['7']=7,V['8']=8,V['9']=9;
V['T']=V['J']=V['Q']=V['K']=10;
REP(i,2,22)
DREP(j,21,1)
if(j>=i)G[i][j]=1;
else REP(t,1,11)G[i][j]+=G[i][j+t]*F[t];
DREP(a,21,1)
REP(b,2,22){
DB Tmp=0;
REP(t,1,11)Tmp+=DP[a+t][b]*F[t];
DP[a][b]=max(1-G[a][b],Tmp);
}
scanf("%d",&Test);
while(Test--){
scanf("%s",C);
int a,b;a=b=0;
REP(i,0,2)a+=V[C[i]];
REP(i,2,4)b+=V[C[i]];
puts(DP[a][b]>=0.5?"YES":"NO");
}
return 0;
}
T6 H
写法十分奇怪。
网上怎么全是状压DP啊?
这里通过..算了自行看注释吧。
Fi,j,k
为抽了i张牌后,
还有j次抽牌机会,
抽到了k张伤害牌的概率(分数表示)
然后复杂度是
O((n+m)nm+pm2)
的,似乎比
O(2m)
要小。
嗯,这个还能写更大一点的数据范围。
代码
#include<bits/stdc++.h>
using namespace std;
#define Komachi is retarded
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define chkmax(a,b) a=max(a,b)
#define chkmin(a,b) a=min(a,b)
#define DB double
#define LL long long
#define M 24
#define Mm 1004
int T;
int p,n,m,atk[M];
LL Gcd(LL a,LL b){return b?Gcd(b,a%b):a;}
struct Node{
LL a,b;
Node Easy(){LL d=Gcd(a,b);a/=d,b/=d;return *this;}
Node operator +(const Node &_){return ((Node){a*_.b+b*_.a,b*_.b}).Easy();}
Node operator *(const Node &_){return ((Node){a*_.a,b*_.b}).Easy();}
void Add(const Node &_){(*this)=(*this)+_;}
};//就是分数结构体
Node F[M][M][M];
int G[M][Mm];
int main(){
scanf("%d",&T);
while(T--){
scanf("%d %d %d",&p,&n,&m);
REP(i,0,m) scanf("%d",&atk[i]);
REP(i,0,M) REP(j,0,M) REP(k,0,M) F[i][j][k]=(Node){0,1};
F[0][1][0]=(Node){1,1};
REP(i,0,n+m) REP(j,1,n+2) REP(k,0,m+1){
Node Val=F[i][j][k]; if(Val.a==0)continue;
F[i+1][j-1][k+1].Add(Val*(Node){m-k,n+m-i});
F[i+1][j+1][k].Add(Val*(Node){n-i+k,n+m-i});
}
memset(G,0,sizeof(G));
G[0][0]=1;
REP(v,0,m){
DREP(i,m-1,-1)
REP(j,0,p+1)G[i+1][min(p,j+atk[v])]+=G[i][j];
//G 取i张牌造成j点伤害的方案数。
}
Node Ans=(Node){0,1};
REP(i,1,m+1){
Node Tmp=(Node){G[i][p],1};
REP(j,0,i) Tmp=Tmp*(Node){1,m-j};
REP(j,1,i+1) Tmp=Tmp*(Node){j,1};
//Tmp为取i张B类牌打死敌人的概率
REP(j,1,n+m+1)if(F[j][0][i].a)Ans.Add(F[j][0][i]*Tmp);
REP(j,1,n+2)if(F[n+m][j][i].a)Ans.Add(F[n+m][j][i]*Tmp);
}
printf("%d",Ans.a);putchar('/');
printf("%d\n",Ans.b);
}
return 0;
}