bzoj2337 XOR和路径
题目大意:无向图中,每个点等概率的选择周围的点走过去,求1~n路径权值(走过边的异或和)的期望。
思路:对边权的二进制每一位单独考虑,因为异或后互不影响。设f[i]表示i~n的路径权值为1的概率,对于每一个点列出方程(注意边权为0/1),高斯消元求出答案。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 20005 #define maxn 105 #define up 30 using namespace std; struct use{ double num[maxn]; }a[maxn],b; int point[maxn]={0},next[maxnode]={0},en[maxnode]={0},va[maxnode][50]={0},tot=0, mi[50]={0},du[maxn]={0},n; void add(int u,int v,int w) { int i=0,x; ++tot;next[tot]=point[u];point[u]=tot;en[tot]=v; x=w;i=0; while(x) {va[tot][++i]=x%2;x/=2;} if (u==v) return; ++tot;next[tot]=point[v];point[v]=tot;en[tot]=u; x=w;i=0; while(x) {va[tot][++i]=x%2;x/=2;} } void gauss() { int i,j,k;double t; for (i=1;i<=n;++i) { if (a[i].num[i]==0) for (j=i+1;j<=n;++j) if (a[j].num[i]!=0){swap(a[i],a[j]);break;} for (j=i+1;j<=n;++j) { t=a[j].num[i]*1.0/a[i].num[i]; for (k=1;k<=n+1;++k) a[j].num[k]-=a[i].num[k]*t; } } for (i=n;i>=1;--i) { b.num[i]=a[i].num[n+1]*1.0/a[i].num[i]; for (j=i-1;j>=1;--j) { a[j].num[n+1]-=b.num[i]*a[j].num[i]; a[j].num[i]=0; } } } int main() { int i,j,m,u,v,w,t;double ans=0; mi[1]=1; for (i=2;i<=up;++i) mi[i]=mi[i-1]*2; scanf("%d%d",&n,&m); for (i=1;i<=m;++i) { scanf("%d%d%d",&u,&v,&w);add(u,v,w); ++du[u];if (u!=v) ++du[v]; } for (t=1;t<=up;++t) { memset(a,0,sizeof(a)); memset(b.num,0,sizeof(b.num)); for (i=1;i<n;++i) { a[i].num[i]=-du[i]; for (j=point[i];j;j=next[j]) { if (va[j][t]==0) ++a[i].num[en[j]]; else { --a[i].num[en[j]];--a[i].num[n+1]; } } } a[n].num[n]=1; gauss();ans+=b.num[1]*mi[t]; } printf("%.3f ",ans); }
bzoj1076 奖励关
题目大意:一共有k轮选择,有n个物品的价值和选它们之前必需先选的物品,求最优策略下的平均价值。
思路:状压dp,f[i][j]表示第i轮前取得物品为集合j的平均价值,如果可以取物品t,f[i][j]+=max(f[i+1][j],f[i+1][j|(1<<t)]+ai[t])/n,如果不能取f[i][j]+=f[i+1][j]/n,答案就是f[1][0]。这里要倒着dp,原因是如果正着的话,无法确定答案是哪一个。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> using namespace std; double fi[105][40000]={0}; int ai[20]={0},pre[20]={0}; int main(){ int n,k,i,j,t,up;scanf("%d%d",&k,&n); for (i=1;i<=n;++i){ scanf("%d",&ai[i]); while(scanf("%d",&j)==1){ if (j==0) break; pre[i]|=1<<(j-1); } }up=1<<n; for (i=k;i;--i) for (j=0;j<up;++j){ for (t=1;t<=n;++t){ if ((pre[t]&j)==pre[t]) fi[i][j]+=max(fi[i+1][j],fi[i+1][j|(1<<t-1)]+ai[t]); else fi[i][j]+=fi[i+1][j]; } fi[i][j]/=n*1.0; }printf("%.6f ",fi[1][0]); }
bzoj3566 概率充电器(!!!)
题目大意:给定一棵树,点发光的概率和边发光的概率,求树上发光点的期望。
思路:因为这棵树可以上下来回更新所以一开始并不会做(orz va)。后来才知道,如果仅考虑下面对答案的影响,只需要从儿子更新过来就行了;如果考虑上面的影响,要考虑父亲的其他儿子对这个点的影响。同时如果这个点本身发光的概率是x,影响它的发光概率是y,则答案变为(1-x)*y+x。注意上下dp数组中只能有一个包含点自己的信息,同时没有信息的那一个要注意上下更新的时候把这个点的信息加进去。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #define maxm 500005 using namespace std; int point[maxm]={0},next[maxm*2]={0},en[maxm*2]={0},tot=0,son[maxm][2]={0}; double lsum[maxm]={0},rsum[maxm]={0},va[maxm*2]={0},val[maxm]={0},fi[2][maxm]={0}; void add(int u,int v,double vaa){ next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=vaa; next[++tot]=point[v];point[v]=tot;en[tot]=u;va[tot]=vaa; } double calc(double x,double y){return x+(1.0-x)*y;} void dp1(int u,int fa){ int v,i,j; for (i=point[u];i;i=next[i]){ v=en[i];if (v==fa) continue; dp1(v,u);fi[0][u]=calc(fi[0][u],calc(fi[0][v],val[v])*va[i]); } } void dp2(int u,int fa){ int v,i,j=0;fi[1][u]=calc(fi[1][u],val[u]); for (i=point[u];i;i=next[i]){ if (en[i]==fa) continue; son[++j][0]=en[i];son[j][1]=i; }lsum[0]=rsum[j+1]=0.0; for (i=1;i<=j;++i) lsum[i]=calc(lsum[i-1],calc(val[son[i][0]],fi[0][son[i][0]])*va[son[i][1]]); for (i=j;i;--i) rsum[i]=calc(rsum[i+1],calc(val[son[i][0]],fi[0][son[i][0]])*va[son[i][1]]); for (i=1;i<=j;++i) fi[1][son[i][0]]=va[son[i][1]]*calc(calc(fi[1][u],lsum[i-1]),rsum[i+1]); for (i=point[u];i;i=next[i]){if(en[i]==fa)continue;dp2(en[i],u);} } int main(){ int n,i,j,u,v;double vaa,ans=0.0;scanf("%d",&n); for (i=1;i<n;++i){ scanf("%d%d%lf",&u,&v,&vaa);add(u,v,vaa*1.0/100.0); }for (i=1;i<=n;++i){scanf("%lf",&val[i]);val[i]=val[i]*1.0/100.0;} dp1(1,0);dp2(1,0); for (i=1;i<=n;++i) ans+=calc(fi[1][i],fi[0][i]); printf("%.6f ",ans); }
bzoj3191 卡牌游戏
题目大意:约瑟夫问题变形:m张牌,每次随机抽一张牌,排上数字x,从0报数到x-1的人出局,问每个人获胜概率。
思路:小学生百科告诉我们关于约瑟夫问题是可以递推求出n个人x固定的时候胜利的人的,f[i]表示i个人中获胜的人是谁,f[i]=(f[i-1]+x)%n,考虑i个人的第一轮中,第x个人出局后,所有人从x,x+1...0,1,x-2重新编号为0,1...n-2,那么又变成了一个规模较小的问题,同时上一次的获胜者y在第一轮中应该位于(y+x)%n的位置。这道题目中,设fi[i][j]表示i个人的时候,第j个人获胜的概率,fi[i][j]=sigma((k+mi)%i==j)f[i-1][k]/m,初始化fi[1][0]=100(百分数表示),最后输出fi[n][]就可以了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; double fi[100][100]={0.0}; int mi[100]={0}; int main(){ int n,m,i,j,k;scanf("%d%d",&n,&m); for (i=1;i<=m;++i) scanf("%d",&mi[i]); fi[1][0]=100; for (i=1;i<n;++i) for (j=0;j<n;++j) for (k=1;k<=m;++k) fi[i+1][(j+mi[k])%(i+1)]+=fi[i][j]/(double)m; for (i=0;i<n-1;++i) printf("%.2f%% ",fi[n][i]); printf("%.2f%% ",fi[n][n-1]); }
bzoj2134 单选错位
题目大意:有一个人做题的时候第i道题答案写到了第i+1道题的位置(n在1的位置),这个人做的题一定做对了,求这个人做对题的期望。
思路:考虑每道题最对的贡献就是这个题和后面的题相同的时候的次数/总次数。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define LL long long #define maxm 10000005 #define p 100000001 using namespace std; LL ai[maxm]={0}; int main(){ int n,i;LL a,b,c;double ans=0.0,x,y; scanf("%d%I64d%I64d%I64d%I64d",&n,&a,&b,&c,&ai[1]); for (i=2;i<=n;++i) ai[i]=(a*ai[i-1]+b)%p; for (i=1;i<=n;++i) ai[i]=ai[i]%c+1; for (i=1;i<=n;++i){ x=(double)ai[i]*1.0;y=(double)ai[i==n ? 1 : i+1]*1.0; ans+=1.0/x/y*min(x,y); }printf("%.3f ",ans); }
bc57 T2
题目大意:给定n个区间,求取出一些区间并集的期望长度*2^n。
思路:考虑每一小段对答案的贡献就是覆盖着一小段的个数为x时,len*(2^x-1)(2^(n-x))(这x段除空集随便取,其他的可以随便取),所以要求出覆盖每一小段的个数,用差分搞一下就可以了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 100005 #define p 1000000007LL #define LL long long using namespace std; struct use{ LL li,ri; }ai[maxm]={0}; LL jie[maxm]={0},bi[maxm*2]={0}; int sum[maxm*2]={0}; int cmp(const use&x,const use&y){return x.li==y.li?x.ri<y.ri:x.li<y.li;} int main(){ int t,i,j,n,l,r;LL ans;scanf("%d",&t);jie[0]=1LL; for (i=1;i<=100000;++i) jie[i]=jie[i-1]*(LL)2%p; while(t--){ scanf("%d",&n);bi[0]=0;ans=0; memset(sum,0,sizeof(sum)); for (i=1;i<=n;++i){ scanf("%I64d%I64d",&ai[i].li,&ai[i].ri); bi[(int)(++bi[0])]=ai[i].li;bi[(int)(++bi[0])]=ai[i].ri; }sort(bi+1,bi+(int)bi[0]+1); int siz=unique(bi+1,bi+(int)bi[0]+1)-bi-1; for (i=1;i<=n;++i){ l=upper_bound(bi+1,bi+siz+1,ai[i].li)-bi-1; r=upper_bound(bi+1,bi+siz+1,ai[i].ri)-bi-1; ++sum[l];--sum[r]; }for (i=1;i<=siz+1;++i) sum[i]+=sum[i-1]; for (i=1;i<=siz;++i) ans=(ans+((jie[sum[i]]-1)%p+p)%p*jie[n-sum[i]]%p*(bi[i+1]-bi[i])%p)%p; printf("%I64d ",ans); } }
bzoj4008 亚瑟王(!!!)
题目大意:给定n张牌以及它们选中的概率和价值,进行r轮游戏,每轮游戏按从1~n张牌的顺序选择,如果这张牌选了就跳过。求期望价值。
思路:设fi[i][j]表示到第i张牌还有j轮没有进行的概率。然后就可以转移,考虑第i+1张牌,如果这张牌选了就是fi[i+1][j-1]+=fi[i][j]*(1-gi[i+1][j]),如果没选就是fi[i+1][j]+=fi[i][j]*gi[i+1][j],同时可以更新答案就是ans+=fi[i][j]*di[i+1]*(1-gi[i+1][j])。gi[i][j]=(1-pi[i])^j。
注意下标!!!
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #define maxm 500 using namespace std; double pi[maxm]={0},di[maxm]={0},fi[maxm][maxm]={0},gi[maxm][maxm]={0}; int main(){ int n,r,i,j,t;double ans,ci;scanf("%d",&t); while(t--){ ans=0.0;scanf("%d%d",&n,&r); memset(fi,0,sizeof(fi)); for (i=1;i<=n;++i) scanf("%lf%lf",&pi[i],&di[i]); for (i=1;i<=n;++i){ gi[i][0]=1.0; for (j=1;j<=r;++j) gi[i][j]=gi[i][j-1]*(1.0-pi[i]); }fi[0][r]=1; for (i=0;i<=n;++i) for (j=0;j<=r;++j){ fi[i+1][j]+=fi[i][j]*gi[i+1][j]; if (j){ fi[i+1][j-1]+=fi[i][j]*(1.0-gi[i+1][j]); ans+=fi[i][j]*di[i+1]*(1-gi[i+1][j]); } }printf("%.10f ",ans); } }
bzoj4318 OSU!
题目大意:共n轮游戏,给定每一轮胜利的概率,连续胜利x场对答案的贡献是x^3,求答案的期望。
思路:对三次方的式子展开,然后对1~3次的更新一下。(0->1也是符合的)。注意3次的是累加上去的,所以有一些1-x和x正好消去了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 100005 using namespace std; int main(){ int i,n;double x,ai,bi,ci; scanf("%d",&n);ai=bi=ci=0.0; for (i=1;i<=n;++i){ scanf("%lf",&x); ai+=x*(3.0*bi+3.0*ci+1.0); bi=x*(bi+2.0*ci+1.0);ci=x*(ci+1.0); }printf("%.1f ",ai); }
同样是对三次方的式子展开,然后这样的答案里面有加上这一位为1、上一位为0时的贡献(这里就要*(1-x[i-1])),还有最后每一位对答案的贡献是这一位后面是0的贡献,所以*(1-xi[i+1])。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 100005 using namespace std; double xi[maxm]; int main(){ int i,n;double x,ai,bi,ci,di,ans; scanf("%d",&n);ai=bi=ci=di=ans=0.0; for (i=1;i<=n;++i) scanf("%lf",&xi[i]); for (i=1;i<=n;++i){ ai=xi[i]*(ai+3.0*bi+3.0*ci+di+1.0-xi[i-1]); bi=xi[i]*(bi+2.0*ci+di+1.0-xi[i-1]); ci=xi[i]*(ci+di+1.0-xi[i-1]); di=xi[i]*(di+1.0-xi[i-1]); ans+=ai*(1.0-xi[i+1]); }printf("%.1f ",ans); }
bc63 balls
题目大意:给定n个球,每个球为i颜色的概率,有x个球颜色相同对答案的贡献是x^2,求期望。
思路:同上,维护一次两次的,这里的不同颜色其实是互不影响的,所以可以用一维数组记录着做,同时这里更新的时候要注意式子,因为这里是可以不连续的。(注意这一位选或不选的时候x、(1-x))
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxm 1005 using namespace std; double fi[maxm],gi[maxm],ai[maxm][maxm]; int main(){ int n,m,i,j;double ci,ans; while(scanf("%d%d",&n,&m)==2){ memset(fi,0,sizeof(fi)); memset(gi,0,sizeof(gi)); for (i=1;i<=n;++i){ ci=0.0; for (j=1;j<=m;++j){ scanf("%lf",&ai[i][j]);ci+=ai[i][j]; }for (j=1;j<=m;++j) ai[i][j]=ai[i][j]*1.0/ci; }for (i=1;i<=n;++i) for (j=1;j<=m;++j){ fi[j]+=ai[i][j]*(gi[j]*2.0+1.0); gi[j]+=ai[i][j]; }ans=0.0; for (i=1;i<=m;++i) ans+=fi[i]; printf("%.2f ",ans); } }
bc67 Black Jack
题目大意:21点游戏简化版,1~9的分数是1~9,其他的都是10,已知闲家和庄家最开始分别的两张牌,然后闲家先操作,庄家后操作,判断闲家胜的概率是否大于0.5。操作是指:一直叫牌(等概率抽到13种牌)直到停止叫牌(可以不叫牌)。
思路:因为两个人各操作一次,所以可以dp一下。fi[i][j]表示闲家叫牌,闲家i庄家j胜的概率;gi[i][j]表示庄家叫牌,闲家i庄家j胜的概率。先dp算fi,从等概率抽牌和不抽牌中取max,不抽牌的时候就是在dp算gi。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define up 100 #define LD double using namespace std; char ss[up]; LD fi[up][up],gi[up][up]; bool vf[up][up]={false},vg[up][up]={false}; int idx(char ch){ if (ch=='A') return 1; if (ch>='2'&&ch<='9') return ch-'0'; return 10;} int id(int x){return (x<=9 ? x : 10);} LD dp(int x,int y){ if (vg[x][y]) return gi[x][y]; vg[x][y]=true; if (y>21) return gi[x][y]=1.; if (y>=x) return gi[x][y]=0.; int i,j;LD ci=0.; for (i=1;i<=13;++i) ci+=dp(x,y+id(i))/13.; return gi[x][y]=ci; } LD pre(int x,int y){ if (vf[x][y]) return fi[x][y]; vf[x][y]=true; if (x>21) return fi[x][y]=0.; int i,j;LD ci=0.; for (i=1;i<=13;++i) ci+=pre(x+id(i),y)/13.; return fi[x][y]=max(dp(x,y),ci); } int main(){ int t,i,j;scanf("%d",&t); for (i=2;i<=20;++i) for (j=2;j<=20;++j) pre(i,j); while(t--){ scanf("%s",&ss); i=idx(ss[0])+idx(ss[1]); j=idx(ss[2])+idx(ss[3]); if (fi[i][j]>0.5) printf("YES "); else printf("NO "); } }
bzoj3143 游走
题目大意:给定一个无向图,从每个点等概率的走到其他连接的点,花费是路径编号的和,从1走到n,现在给边重新编号,求最小的期望花费。
思路:考虑到每个点的期望,可以用gauss消元算出来(把n点的期望看作0,因为到n就不能走了),然后就可以算出边的期望,编号小的一定是期望大的,算出答案就可以了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 505 #define M 250005 #define LD long double #define eps 1e-9 using namespace std; int du[N]={0},tot=0,n; LD per[N],ai[N][N]={0},bi[N]={0},ci[M]={0}; struct use{int st,en;}edge[M]; int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} void gauss(){ int i,j,k;LD ki; for (i=1;i<n;++i){ if (cmp(ai[i][i],0.)==0) for (j=i+1;j<n;++j) if (cmp(ai[j][i],0.)!=0){ for (k=1;k<n;++k) swap(ai[j][k],ai[i][k]); swap(bi[j],bi[i]);break;} for (j=i+1;j<n;++j){ ki=ai[j][i]/ai[i][i]; for (k=i;k<n;++k) ai[j][k]-=ai[i][k]*ki; bi[j]-=bi[i]*ki;} }for (i=n-1;i;--i){ ai[i][i]=bi[i]/ai[i][i]; for (j=i-1;j;--j) bi[j]-=ai[i][i]*ai[j][i];} } int main(){ int i,j,m,u,v;LD ans=0.; scanf("%d%d",&n,&m); for (i=1;i<=m;++i){ scanf("%d%d",&u,&v); ++du[u];++du[v];edge[i]=(use){u,v}; }for (i=1;i<=n;++i){per[i]=1./(LD)du[i];ai[i][i]=1.;} bi[1]=1.;ai[n][n]=0.; for (i=1;i<=m;++i){ u=edge[i].st;v=edge[i].en; if (u==n||v==n) continue; ai[u][v]-=per[v];ai[v][u]-=per[u]; }gauss(); for (i=1;i<=m;++i){ u=edge[i].st;v=edge[i].en; ci[i]+=ai[u][u]*per[u]+ai[v][v]*per[v]; }sort(ci+1,ci+m+1); for (i=1;i<=m;++i) ans+=ci[i]*(m-i+1); printf("%.3f ",(double)ans); }
bzoj1415 聪聪和可可
题目大意:给定无向图和聪聪、可可的位置,每秒聪聪先走,走向最靠近可可的位置(距离相同选编好小的,如果一步没到一定会走两步),可可等概率选择相邻点或者不动,求聪聪吃到可可的期望时间。
思路:首先预处理gi[i][j]表示聪聪在i可可在j,聪聪走一步到离j最近的点是哪个,然后就可以dp了。记忆化搜索,fi[i][j]表示聪聪在i可可在j的期望步数,i=j的时候期望是0;gi[i][j]=j||gi[gi[i][j]][j]=j,fi[i][j]=1;否则枚举j能到的点更新期望。
注意:如果聪聪一步没到可可,那么聪聪一定会走两步,不管答案是否更优。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 1005 #define len 1000000 #define inf 2100000000 #define LD double using namespace std; LD fi[N][N]={0.},du[N]={0.}; int tot=0,point[N],next[N*2],en[N*2],dis[N][N],que[len+1],gi[N][N]={0}; bool visit[N],vi[N][N]={false}; void add(int u,int v){ next[++tot]=point[u];point[u]=tot;en[tot]=v; next[++tot]=point[v];point[v]=tot;en[tot]=u; du[u]+=1.;du[v]+=1.;} void spfa(int rt){ int u,v,i,head=0,tail; memset(visit,false,sizeof(visit)); visit[que[tail=1]=rt]=true; dis[rt][rt]=0; while(head!=tail){ visit[u=que[head=head%len+1]]=false; for (i=point[u];i;i=next[i]){ if (dis[v=en[i]][rt]>=dis[u][rt]+1){ if (dis[v][rt]>dis[u][rt]+1) gi[v][rt]=u; else gi[v][rt]=min(gi[v][rt],u); dis[v][rt]=dis[u][rt]+1; if (!visit[v]) visit[que[tail=tail%len+1]=v]=true; } } } } LD dp(int x,int y){ if (vi[x][y]) return fi[x][y]; vi[x][y]=true; if (x==y) return fi[x][y]=0.; int i,j; if (gi[x][y]==y||gi[gi[x][y]][y]==y) return fi[x][y]=1.; for (i=point[y];i;i=next[i]){ j=en[i]; fi[x][y]+=dp(gi[gi[x][y]][y],j)+1.; }fi[x][y]+=dp(gi[gi[x][y]][y],y)+1.; return fi[x][y]=fi[x][y]*1./(du[y]+1.); } int main(){ int n,e,c,m,i,j,u,v; scanf("%d%d%d%d",&n,&e,&c,&m); for (i=1;i<=e;++i){scanf("%d%d",&u,&v);add(u,v);} memset(dis,127,sizeof(dis)); for (i=1;i<=n;++i) spfa(i); printf("%.3f ",dp(c,m)); }
bzoj2707 走迷宫
题目大意:给定一个有向图,求从S到T的期望步数。如果可能到不了,就输出INF。(最大强连通分量不超过100个点)
思路:设fi[i]表示i到T的期望步数,fi[i]=sigma(i->j)fi[j]/out[i] + 1。缩点之后,对于每个强连通分量(可能是一个点)算期望,边拓扑,边算答案。
注意:1)因为有自环的存在,所以一个点的也要列出方程求解;
2)期望的转移;
3)到终点不能再走的题目多数是表示到终点的期望或概率。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 10005 #define M 1000005 #define up 105 #define LD double #define eps 1e-9 using namespace std; int point[N]={0},next[M],en[M],tot=0,n,m,s,t,que[N]={0},po[N]={0},ne[N],out[N]={0}, sccno[N]={0},scnt=0,zh[N]={0},pre[N]={0},low[N]={0},in[N]={0},siz[N]={0}, po1[N]={0},ne1[M],en1[M],t1=0,t2=0,po2[N]={0},ne2[M],en2[M]; LD fi[N]={0.},ai[up][up],bi[up]; bool v1[N]={false},v2[N]={false}; int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;++out[u];} void add1(int u,int v){ne1[++t1]=po1[u];po1[u]=t1;en1[t1]=v;} void add2(int u,int v){ne2[++t2]=po2[u];po2[u]=t2;en2[t2]=v;++in[v];} void dfs(int u){ int i,v;v1[u]=true; for (i=point[u];i;i=next[i]) if (!v1[v=en[i]]) dfs(v);} void dfs2(int u){ int i,v;v2[u]=true; for (i=po1[u];i;i=ne1[i]) if (!v2[v=en1[i]]) dfs2(v);} void tarjan(int u){ int i,v;pre[u]=low[u]=++tot; zh[++zh[0]]=u; for (i=point[u];i;i=next[i]){ if (!pre[v=en[i]]){ tarjan(v);low[u]=min(low[u],low[v]); }else if (!sccno[v]) low[u]=min(low[u],pre[v]); }if (low[u]==pre[u]){ ++scnt; while(zh[0]){ sccno[v=zh[zh[0]--]]=scnt; ne[v]=po[scnt];po[scnt]=v; ++siz[scnt];if (v==u) break; } }} void gauss(){ int i,j,k;LD kk; for (i=1;i<=zh[0];++i){ if (cmp(ai[i][i],0.)==0) for (j=i+1;j<=zh[0];++j) if (cmp(ai[j][i],0.)!=0){ for (k=0;k<=zh[0];++k) swap(ai[j][k],ai[i][k]); swap(bi[i],bi[j]);break;} for (j=i+1;j<=zh[0];++j){ kk=ai[j][i]/ai[i][i]; for (k=i;k<=zh[0];++k) ai[j][k]-=ai[i][k]*kk; bi[j]-=bi[i]*kk;} }for (i=zh[0];i;--i){ bi[i]=bi[i]/ai[i][i]; for (j=i-1;j;--j) bi[j]-=ai[j][i]*bi[i]; } } void calc(int u){ int i,j,v,x;zh[0]=0; if (u==sccno[t]){fi[t]=0.;return;} for (i=po[u];i;i=ne[i]){ zh[++zh[0]]=i;pre[i]=zh[0]; }for (i=1;i<=zh[0];++i){ for (j=1;j<=zh[0];++j) ai[i][j]=0.; bi[i]=0.; }for (i=1;i<=zh[0];++i){ for (j=point[x=zh[i]];j;j=next[j]){ if (sccno[v=en[j]]!=sccno[x]) bi[i]+=fi[v]; else --ai[pre[x]][pre[v]]; }ai[i][i]+=out[x];bi[i]+=out[x]; }gauss(); for (i=po[u];i;i=ne[i]) fi[i]=bi[pre[i]]; } void work(){ int i,j,u,head=0,tail=0; for (i=1;i<=scnt;++i) if (!in[i]) que[++tail]=i; while(head<tail){ u=que[++head];calc(u); for (j=po2[u];j;j=ne2[j]){ --in[en2[j]]; if (!in[en2[j]]) que[++tail]=en2[j]; } } } int main(){ int i,j,u,v;bool f=false; scanf("%d%d%d%d",&n,&m,&s,&t); for (i=1;i<=m;++i){ scanf("%d%d",&u,&v); if (u!=t){add(u,v);add1(v,u);} }dfs(s);dfs2(t); for (i=1;i<=n;++i) if (v1[i]&&!v2[i]) f=true; if (f) printf("INF "); else{ for (i=1;i<=n;++i) if (!pre[i]) tarjan(i); for (i=1;i<=n;++i) for (j=point[i];j;j=next[j]) if(sccno[en[j]]!=sccno[i]) add2(sccno[en[j]],sccno[i]); work();printf("%.3f ",fi[s]); } }
省队集训 理综考试(!!!)
题目大意:已知n个人的得分区间,求每个人得第i名的概率。
思路:预处理出x这个人选y这个区间的时候能取到y这个区间的人中,fi[i][j][k]表示前i个人、j个选这个区间之前的、k个人选这个区间的概率,然后统计答案的时候枚举这个人在这些能选这个区间的人中前面的人数、之前的、这个区间的,然后算出概率*这个人选这个区间的概率。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 165 #define LD double using namespace std; int li[N],ri[N],ci[N],cz=0,id[N][N],cnt[N]={0},cl[N]={0},cr[N]={0},di[N],dz; LD fi[N][N][N],gi[N][N][N],ans[N][N],bl[N][N],bi[N][N],br[N][N]; int main(){ freopen("gg.in","r",stdin); freopen("gg.out","w",stdout); int n,i,j,k,a,b;scanf("%d",&n); for (i=1;i<=n;++i){ scanf("%d%d",&li[i],&ri[i]); ci[++cz]=li[i];ci[++cz]=ri[i]; }sort(ci+1,ci+cz+1); cz=unique(ci+1,ci+cz+1)-ci-1; for (i=1;i<cz;++i) for (j=1;j<=n;++j){ if (ri[j]<=ci[i]) ++cl[i]; else if (li[j]>=ci[i+1]) ++cr[i]; else id[i][++cnt[i]]=j; } for (i=1;i<=n;++i) for (j=1;j<cz;++j){ if (li[i]>=ci[j+1]||ri[i]<=ci[j]) continue; bl[i][j]=(LD)(ci[j]-li[i])*1./(LD)(ri[i]-li[i]); bi[i][j]=(LD)(ci[j+1]-ci[j])*1./(LD)(ri[i]-li[i]); br[i][j]=(LD)(ri[i]-ci[j+1])*1./(LD)(ri[i]-li[i]); } memset(ans,0,sizeof(ans)); for (i=1;i<=n;++i) for (j=1;j<cz;++j){ if (li[i]>=ci[j+1]||ri[i]<=ci[j]) continue; fi[0][0][0]=1.;dz=0; for (k=1;k<=cnt[j];++k){ if (id[j][k]==i) continue; di[++dz]=id[j][k]; }for (k=1;k<=dz;++k) for (a=0;a<=k;++a) for (b=0;b<=k-a;++b){ fi[k][a][b]=0.; if (a) fi[k][a][b]+=fi[k-1][a-1][b]*bl[di[k]][j]; if (b) fi[k][a][b]+=fi[k-1][a][b-1]*bi[di[k]][j]; fi[k][a][b]+=fi[k-1][a][b]*br[di[k]][j]; } for (k=0;k<=dz;++k) for (a=0;a<=dz-k;++a) for (b=0;b<=k;++b) ans[i][cr[j]+k+1]+=1./(dz-a-b+1)*fi[dz][a][dz-a-b]*bi[i][j]; } for (i=1;i<=n;++i){ for (j=1;j<=n;++j) printf("%.8f ",ans[i][j]); printf(" "); } }
noi模拟赛 哈夫曼树(!!!)
题目大意:n的数得到一个哈夫曼树,每次随机选择两个,求所有方案的代价和。
思路:所有的方案就是all=∏(i=2~n)i*(i-1)/2,可以发现每个数在所有方案中出现的次数一样,所以可以只统计1合并的次数,*sigma ai就可以了。考虑n-1次合并,i个数的时候,共有i*(i-1)/2的合并方案,其中i-1种用到1所在的点,所以1合并的次数+all*2/i。
orz vaorz vaorz va
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define LL long long #define p 1000000007LL using namespace std; int in(){ char ch=getchar();int x=0; while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9') { x=x*10+ch-'0';ch=getchar(); }return x;} LL mi(LL x,LL y){ LL a=1LL; for (;y;y>>=1LL){ if (y&1LL) a=a*x%p; x=x*x%p; }return a;} int main(){ freopen("huffman.in","r",stdin); freopen("huffman.out","w",stdout); int n,i;LL x,sm=0LL,cc=0LL,ci=1LL;n=in(); for (i=1;i<=n;++i){ x=(LL)in();sm+=x; if (i>1) ci=ci*((LL)i*(i-1)/2)%p; }for (i=2;i<=n;++i) cc=(cc+ci*2LL*mi((LL)i,p-2LL))%p; printf("%I64d ",cc*sm%p); }
bzoj2878 迷失游乐园(!!)
题目大意:给出一棵环套树,随机选一个点开始走,等概率走向周围没有经过的点,问走的路径长度期望。
思路:树上的比较好求,从终点开始走,更新出每个点的up和down,gi[u][0]=sigma 1/(du[u]-1)*(gi[v][0]+va[i]),最后一个点的时候(也就是起点)是/du[u]。有环的时候,先找出所有外向树的up,用这个up去更新其他的up(中间要用另一个数组存,防止改变最初的up)。有环的时候的概率是(gi[u][0]+ci*(du[u]-2)/(du[u]-1))*cc。cc表示环上选这条路径的概率,ci表示环上链的权值和,ci后面乘的式子是因为外向树中所带来的概率的和不是树上的(du[u]-1)/du[u],而是(du[u]-2)/du[u],所以有所改变。对于环上相邻两点绕了一圈的情况终点的处的概率是1/(du[u]-2),因为环上的两个点都不能走。
注意:一定要考虑好概率的转移和环上相邻点的特殊情况。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 100005 #define M 200005 #define LD double #define eps 1e-9 using namespace std; int in(){ char ch=getchar();int x=0; while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar(); }return x;} int cmp(LD x,LD y){ if (x-y>eps) return 1; if (y-x>eps) return -1; return 0;} int n,m,rt,point[N]={0},next[M],en[M],tot,du[N]={0},fe[N]={0},hu[N],hz=0,zh[N],zt=0; LD va[M],fi[N],gi[N][2],hv[N]; bool oh[N]={false},hh=false,vi[N]={false}; void add(int u,int v,LD w){ next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=w; next[++tot]=point[v];point[v]=tot;en[tot]=u;va[tot]=w; ++du[u];++du[v]; } void up(int u,int fa){ int i,v;gi[u][0]=0.; for (i=point[u];i;i=next[i]){ if ((v=en[i])==fa) continue; up(v,u);gi[u][0]+=1./(du[u]-1)*(gi[v][0]+va[i]); } } void down(int u,int fa,LD vv){ int i,v;fi[u]=0.;gi[u][1]=gi[u][0]; if (fa){ fi[u]+=1./du[u]*(gi[fa][1]-(1./(du[fa]-1)*(gi[u][0]+vv))+vv); if (du[u]>1) gi[u][1]+=1./(du[u]-1)*(gi[fa][1]-(1./(du[fa]-1)*(gi[u][0]+vv))+vv); }for (i=point[u];i;i=next[i]){ if ((v=en[i])==fa) continue; down(v,u,va[i]); fi[u]+=1./du[u]*(gi[v][0]+va[i]); } } void geth(int x){ hz=0; for (;;--zt){ oh[zh[zt]]=true; hu[++hz]=zh[zt]; hv[hz]=va[fe[zh[zt]]]; if (zh[zt]==x) break; } } void find(int u){ int i,v;zh[++zt]=u; vi[u]=true; for (i=point[u];i;i=next[i]){ v=en[i]; if (i==fe[u]||(i==(fe[u]^1))) continue; if (vi[v]){ geth(v);hh=true; hv[hz]=va[i];return; }fe[v]=i;find(v); if (hh) return; }--zt; } void gup(int u){ int i,v;gi[u][0]=0.; vi[u]=true; for (i=point[u];i;i=next[i]){ if (vi[v=en[i]]) continue; gup(v);gi[u][0]+=1./(du[u]-1)*(gi[v][0]+va[i]); } } void gdown(int u,int fa,LD vv){ int i,v;gi[u][1]=gi[u][0]; if (fa){ fi[u]+=1./du[u]*(gi[fa][1]-(1./(du[fa]-1)*(gi[u][0]+vv))+vv); if (du[u]>1) gi[u][1]+=1./(du[u]-1)*(gi[fa][1]-(1./(du[fa]-1)*(gi[u][0]+vv))+vv); }for (i=point[u];i;i=next[i]){ v=en[i]; if (oh[v]||v==fa) continue; gdown(v,u,va[i]); fi[u]+=1./du[u]*(gi[v][0]+va[i]); } } int main(){ int i,j,u,v,w,la;LD ans=0.,ci,cc; n=in();m=in();tot=1; for (i=1;i<=m;++i){ u=in();v=in();w=in(); add(u,v,(LD)w); }if (m==n-1){ for (i=1;i<=n;++i) if (du[i]>1){rt=i;break;} up(rt,0);down(rt,0,0.); for (i=1;i<=n;++i) ans+=1./n*fi[i]; }else{ find(1); for (i=1;i<=n;++i){ vi[i]=oh[i]; fi[i]=gi[i][0]=gi[i][1]=0.; }for (i=1;i<=n;++i) if (oh[i]) gup(i); for (i=1;i<=hz;++i){ u=hu[i];ci=0.;cc=1.; gi[u][1]+=gi[u][0]; for (la=i,j=i%hz+1;j!=i;la=j,j=j%hz+1){ ci+=hv[la];cc/=1.*(du[hu[j]]-1); if (cmp(gi[u][0],0.)>0){ if (j%hz+1!=i){ fi[hu[j]]+=(gi[u][0]+ci*(du[u]-2)/(du[u]-1))* cc*(du[hu[j]]-1)/du[hu[j]]; gi[hu[j]][1]+=(gi[u][0]+ci*(du[u]-2)/(du[u]-1))*cc; }else{ fi[hu[j]]+=(gi[u][0]*(du[u]-1)/(du[u]-2)+ci)*cc*(du[hu[j]]-1)/du[hu[j]]; gi[hu[j]][1]+=(gi[u][0]*(du[u]-1)/(du[u]-2)+ci)*cc; } } }if (du[u]==2){ fi[hu[la]]+=ci*cc*(du[hu[la]]-1)/du[hu[la]]; gi[hu[la]][1]+=ci*cc; }ci=0.;cc=1.; for (la=i,j=(i==1 ? hz : i-1);j!=i;la=j,j=(j==1 ? hz : j-1)){ ci+=hv[j];cc/=1.*(du[hu[j]]-1); if (cmp(gi[u][0],0.)>0){ if ((j==1 ? hz : j-1)!=i){ fi[hu[j]]+=(gi[u][0]+ci*(du[u]-2)/(du[u]-1))* cc*(du[hu[j]]-1)/du[hu[j]]; gi[hu[j]][1]+=(gi[u][0]+ci*(du[u]-2)/(du[u]-1))*cc; }else{ fi[hu[j]]+=(gi[u][0]*(du[u]-1)/(du[u]-2)+ci)*cc*(du[hu[j]]-1)/du[hu[j]]; gi[hu[j]][1]+=(gi[u][0]*(du[u]-1)/(du[u]-2)+ci)*cc; } } }if (du[u]==2){ fi[hu[la]]+=ci*cc*(du[hu[la]]-1)/du[hu[la]]; gi[hu[la]][1]+=ci*cc; } }for (i=1;i<=n;++i) if (oh[i]){gi[i][0]=gi[i][1];gi[i][1]=0.;} for (i=1;i<=n;++i) if (oh[i]) gdown(i,0,0.); for (i=1;i<=n;++i) ans+=1./n*fi[i]; }printf("%.5f ",ans); }