概率与期望

概率期望

P4316 绿豆蛙的归宿

非常基础的概率期望,设 \(f_i\) 表示点i到点n的期望总长度,由于要求起点期望,所以反向连边,倒序更新:\(f_i = \sum_{j \in son_i} \frac{f_j+val_{i,j}}{d_i}\) ,代码如下:

#include<bits/stdc++.h>
using namespace  std;
const long long MAXN=1e5+5;
long long n,m,cd[MAXN],rd[MAXN];
long double dp[MAXN];
struct node{
	long long next,to,dis;
}e[MAXN<<1];
long long head[MAXN],cnt;
void add(long long a,long long b,long long c){
	e[++cnt].to=b;
	e[cnt].next=head[a];
	e[cnt].dis=c;
	head[a]=cnt;
}
queue<long long> q;
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>m;
	if(n==1){
		cout<<"0.00";
		return 0;
	}
	for(long long i=1;i<=m;i++){
		long long x,y,z;
		cin>>x>>y>>z;
		add(y,x,z);	
		cd[x]++,rd[x]++;
	}
	dp[n]=0.0;q.push(n);
	while(!q.empty()){
		long long x=q.front();q.pop();
		for(long long i=head[x];i;i=e[i].next){
			long long y=e[i].to;
			if(cd[y]) dp[y]+=(dp[x]+e[i].dis)/(1.0*cd[y]);
			rd[y]--;
			if(!rd[y]) q.push(y);
		}
	}
	cout<<fixed<<setprecision(2)<<dp[1];
	return 0;
}

P1654 OSU!

\(f_i\) 表示长度为i的串期望得到的值,则答案为 \(f_n\)

考虑转移方程,对于长度为 \(x\) 的串,其贡献为 \(x^3\) ,对于长度为 \(x+1\) 的串,其贡献为 $(x+1)^3 = x^3 + 3x^2 + 3x + 1 $,则增加的贡献为 \(3x^2 + 3x + 1\)

那么我们就可以维护增加的贡献值,设 \(x1c\) 表示式子中的一次期望 ,\(x2c\) 表示式子中的二次期望,逐个更新答案即可;

代码如下:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int MAXN=1e5;
ll n;
long double p[MAXN],x1c[MAXN],x2c[MAXN],ans[MAXN];
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++) cin>>p[i];
	for(int i=1;i<=n;i++){
		x1c[i]=(x1c[i-1]+1)*p[i];
		x2c[i]=(x2c[i-1]+2*x1c[i-1]+1)*p[i];
		ans[i]=ans[i-1]+(3*x2c[i-1]+3*x1c[i-1]+1)*p[i];
	}
	cout<<fixed<<setprecision(1)<<ans[n];
	return 0;
} 

二倍经验:P1365 WJMZBMR打osu! / Easy,对于不确定的问号,对于连续串长度再取一个期望即可;

P4206 [NOI2005] 聪聪与可可

很有意思的一道期望DP;

容易想到需要先预处理出聪聪的行走逻辑 \(nxt_{i,j}\) ,即对于任意一种两者的位置关系,聪聪下一步会选择的行走节点,即是在聪聪所在点的所有相连点中,与可可所在点距离最短,且编号最小的点(若\(i==j\),则 \(nxt_{i,j}=j\));

注意到 \(n<=2000\) ,floyd会超时;但同时 \(m<=2000\),对于稀疏图dijkstra的时间复杂度是优于floyd的,我们可以跑n遍堆优化dijkstra来处理处任意两点的距离;

\(dp_{u,v}\) 表示聪聪在点 \(u\) ,可可在点 \(v\) 时聪聪抓到可可的期望步数,设 \(nxm=nxt_{nxt_{u,v},v}\) 表示聪聪下一次到达的点, 可得状态转移方程:

1、\(dp_{u,v}=0 (nxm=v,u=v)\)

2、\(dp_{u,v}=1 (nxm=v,u \neq v)\)

3、\(dp_{u,v}= \frac{1}{d_v +1} * dp_{nxm,v} + \sum_{w \in son_v} \frac{1}{d_v +1} * dp_{nxm,w} (nxm \neq v)\)

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5;
int n,E,c,m,nxt[1005][1005],dis[1005][1005],d[1005];
int flag[1005][1005],vis[1005][1005];
double dp[1005][1005],ans;
struct edge{
	int next,to;
}e[MAXN<<1];
int head[MAXN],cnt;
void add(int a,int b){
	e[++cnt].to=b;
	e[cnt].next=head[a];
	head[a]=cnt;
}
struct node{
	int d,pos;
	bool operator < (const node &x)const{
		return x.d<d;
	}
};
double dfs(int u,int v){
	if(flag[u][v]) return dp[u][v];
	if(u==v) return 0;
	int nxm=nxt[nxt[u][v]][v];
	if(nxm==v) return 1;
	dp[u][v]=1;
	for(int i=head[v];i;i=e[i].next){
		int w=e[i].to;
		dp[u][v]+=dfs(nxm,w)/(d[v]+1);
	}
	dp[u][v]+=dfs(nxm,v)/(d[v]+1);
	flag[u][v]=1;
	return dp[u][v];
}
void dijkstra(int s){
	dis[s][s]=0;
	priority_queue<node> q;
	q.push((node){0,s});
	while(!q.empty()){
		node tmp=q.top();
		q.pop();
		int x=tmp.pos;
		if(vis[s][x]) continue;
		vis[s][x]=1;
		for(int i=head[x];i;i=e[i].next){
			int y=e[i].to;
			if(dis[s][y]>dis[s][x]+1){
				dis[s][y]=dis[s][x]+1;
				if(!vis[s][y]) q.push((node){dis[s][y],y});
			}
		}
	}
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>E>>c>>m;
	dp[m][c]=0.0;
	memset(dis,0x3f,sizeof dis);
	memset(vis,0,sizeof vis);
	for(int i=1;i<=n;i++) dis[i][i]=0;
	for(int i=1;i<=E;i++){
		int x,y;cin>>x>>y;
		add(x,y);add(y,x);
		d[x]++,d[y]++;
	}
	for(int i=1;i<=n;i++)
		dijkstra(i);
	for(int x=1;x<=n;x++){
		for(int p=1;p<=n;p++){
			if(x==p){
				nxt[x][p]=p;
				continue;
			}
			int minl=1e8,mind;
			for(int i=head[x];i;i=e[i].next){
				int y=e[i].to;
				if(dis[y][p]<minl) minl=dis[y][p],mind=y;
				else if(dis[y][p]==minl&&y<mind) mind=y; 
			}
			nxt[x][p]=mind;
		}
	}
	cout<<fixed<<setprecision(3)<<dfs(c,m);
	return 0;
}

简单朴实的名字总让人感到不安

原题找不到了,就用这个代替吧

树上操作+多次询问,容易联想到lca,事实的确如此。我们预处理出 \(f_i\) 表示点 \(i\) 转移到 其父亲的期望步数,\(g_i\) 表示点 \(i\) 从其父亲转移来的期望步数,用lca+前缀和优化就可以方便的求出每次询问的答案;

为啥这么少,因为真的懒得打了 代码如下:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+10;
const int mod=1e9+7;
int n,q,head[MAXN],cnt,fa[MAXN],c[MAXN],dep[MAXN];
int par[MAXN][25];
int f[MAXN],g[MAXN];//f_i 表示从i转移到fa_i的期望步数;  g_i 表示从fa_i转移到i的期望步数 
struct edge{
	int next,to;
}e[MAXN<<1];
struct node{
	int u,v;
}query[MAXN];
void add(int a,int b){
	e[++cnt].to=b;
	e[cnt].next=head[a];
	head[a]=cnt;
}
void dfs_getf(int x,int ff){
	par[x][0]=ff,dep[x]=dep[ff]+1;
	fa[x]=ff,f[x]=0;
	for(int i=head[x];i;i=e[i].next){
		int y=e[i].to;
		if(y==ff) continue;
		dfs_getf(y,x);
		f[x]=(f[x]+f[y]+1)%mod;
	}
	f[x]=(f[x]+1)%mod;
	return ;
}
void dfs_getg(int x){
	if(x!=1){
		g[x]+=g[fa[x]],g[x]%=mod;
		for(int i=head[fa[x]];i;i=e[i].next){
			int y=e[i].to;
			if(y!=x&&y!=fa[fa[x]]) g[x]=(g[x]+f[y])%mod;
			g[x]=(g[x]+1)%mod;
		}
	}
	for(int i=head[x];i;i=e[i].next){
		int y=e[i].to;
		if(y==fa[x]) continue;
		dfs_getg(y); 
	}
	return ;
}
void dfs(int x){
	f[x]=(f[x]+f[fa[x]])%mod;
	g[x]=(g[x]+g[fa[x]])%mod;
	for(int i=head[x];i;i=e[i].next){
		int y=e[i].to;
		if(y==fa[x]) continue;
		dfs(y);
	}
}
void getp(){
	for(int j=1;(1<<j)<=n;j++)
		for(int i=1;i<=n;i++)
			par[i][j]=par[par[i][j-1]][j-1];
	return ;
}
int lca(int u,int v){
	if(dep[u]>dep[v]) swap(u,v);
	for(int i=22;i>=0;i--){
		if(dep[par[v][i]]>=dep[u]){
			v=par[v][i];
		}
	}
	if(u==v) return u;
	for(int i=22;i>=0;i--){
		if(par[u][i]!=par[v][i]){
			u=par[u][i],v=par[v][i]; 
		}
	}
	return par[u][0];
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>q;
	for(int i=1,x,y;i<n;i++){
		cin>>x>>y;
		add(x,y);add(y,x);
		c[x]++,c[y]++; 
	}
	dfs_getf(1,0);
	f[1]=g[1]=0;
	dfs_getg(1);
	dfs(1);
	getp();
	for(int i=1,u,v;i<=q;i++){
		cin>>u>>v;
		int LCA=lca(u,v);
		cout<<(long long)(((f[u]-f[LCA])%mod+(g[v]-g[LCA])%mod)+mod)%mod<<"\n";
	}
	
//	for(int i=1;i<=n;i++)
//		cout<<f[i]<<" "<<g[i]<<"\n";
	return 0;
} 

概率期望&高斯消元

DP序混乱,往往是指存在两个状态x,y,x可以转移到y,y也可以转移到x;

在非概率/期望DP中不可能存在这种情况,因为这会导致状态值不确定;

然而在概率/期望DP中这种情况却可以存在,一般是x的p/q可以转移到y,y的a/b可以转移到x(即一个无向图);

有一个显然的想法是经过多次转移后逼近精确值,但这样的时间复杂度显然过大,精度也非常容易被卡;

所以我们可以列出多个状态的转移式(这其中必然有循环转移的情况),变式使等式右边成为一个可求值,接下来就可以用高斯消元解出方程组了。

例题:

P3232 [HNOI2013] 游走

题意:给出一个无向图,从1号点到n号点,给每条边编号(从1到m),经过代价为该边编号,求最低期望价值

容易想到求出每条边的期望经过次数然后排序,次数最多的边编号最小,以此类推;

\(E(u,v)\) 表示双端节点为 \(u,v\) 的期望经过次数, \(f_i\) 表示点 \(i\) 的期望经过次数 , \(siz_i\) 表示点 \(i\) 的度;

容易得到: \(E(u,v)= \frac{f_u}{siz_u} + \frac{f_v}{siz_v}\)

因为点的度是已知的,所以重点就是求点的期望经过次数;

对于点n,因为走到它时就会停止,所以 \(f_n=0\) ,在后续计算中也不考虑它;

对于点1,因为是起点,所以必定至少经过1次,那么 \(f_1= \sum \frac{f_v}{siz_v} +1 ( v 与 1 相邻)\)

对于其他点,\(f_u= \sum \frac{f_v}{siz_v} ( v 与 u 相邻)\)

为了方便高斯消元,以上式子等价于

\(-f_i + \sum \frac{f_v}{siz_v} = -1 (i=1)\)

\(-f_u + \sum \frac{f_v}{siz_v} = 0 (1<i<n)\)

这样就非常好写了

#include<bits/stdc++.h>
using namespace std;
const int N=5e2+10,M=13e4+10;
const double eps=1e-6;
int n,m;
vector<int> q[N]; 
double zg[N][N],f[N],siz[N],ans;
struct edge{
	int u,v;
	double val;
}E[M];
bool cmp(edge a,edge b){
	return a.val>b.val;
}
bool gauss(){
	for(int i=1;i<=n;i++){
		int r=i;
		for(int k=i;k<=n;k++)
			if(fabs(zg[k][i])>eps){
				r=k;break;
			}
		if(r!=i) swap(zg[r],zg[i]);
		if(fabs(zg[i][i])<eps) return 0;
		for(int j=n+1;j>=i;j--)
			zg[i][j]/=zg[i][i];
		for(int k=i+1;k<=n;k++)
			for(int j=n+1;j>=i;j--)
				zg[k][j]-=zg[k][i]*zg[i][j];	
	}
	for(int i=n-1;i>=1;i--)
		for(int j=i+1;j<=n;j++)
			zg[i][n+1]-=zg[i][j]*zg[j][n+1];
	return 1;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>E[i].u>>E[i].v;
		q[E[i].u].push_back(E[i].v);
		q[E[i].v].push_back(E[i].u);
		siz[E[i].u]++,siz[E[i].v]++;
	}
	n--;
	zg[1][n+1]=-1.0;
	for(int i=1;i<=n;i++){
		zg[i][i]=-1.0;
		for(int j=0;j<q[i].size();j++){
			if(q[i][j]==n+1) continue;
			zg[i][q[i][j]]=(1.0/siz[q[i][j]]);
		}
	}
	gauss();
	for(int i=1;i<=n;i++) f[i]=zg[i][n+1];
	for(int i=1;i<=m;i++) E[i].val=f[E[i].v]/siz[E[i].v]+f[E[i].u]/siz[E[i].u];
	sort(E+1,E+m+1,cmp);
	for(int i=1;i<=m;i++){
		ans+=E[i].val*i;
	}
	cout<<fixed<<setprecision(3)<<ans;
	return 0;
} 

P3211 [HNOI2011] XOR和路径

期望值异或恐怖如斯

状态其实是非常好想的,设 \(f_i\) 表示从点 \(i\) 出发到达点 \(n\) 的期望值,\(val_{i,j}\) 表示边 \(i,j\) 的值,\(d_i\) 表示点 \(i\) 的度,理所当然的:

$f_i= \sum_{j \in son_i} \frac{val_{i,j} \oplus f_j}{d_i} $

即:$d_i*f_i= \sum_{j \in son_i} val_{i,j} \oplus f_j $

但是考虑到对期望值求异或和完全不可能非常困难,所以需要做一点优化;

因为每一位上的异或和结果是相互不影响的,所以我们可以对式子进行按位拆分,枚举答案的二进制位单独计算,此时\(f_i\)就表示答案当前位是1的概率,对于边的权值也按当前数位计算;

然后根据异或的性质,可以将式子拆成两种情况,即:

\(d_i*f_i= \sum_{val_{i,j}=0} f_j + \sum_{val_{i,j}=1} (1-f_j)\) ,因为对于异或运算 \(x \oplus y\),如果x=0(即此处 \(val_{i,j}=0)\),答案就是y的值(\(0 \oplus 1=1, 0 \oplus0=0\)),同理,如果x是1,答案就是(1-y)的值;

化简得:$d_i*f_i - \sum_{val_{i,j}=0} f_j+ \sum_{val_{i,j}=1} f_j = \sum_{val_{i,j}=1} 1 $

可以发现符合线性方程组的性质,接下来就可以用高斯消元解了

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e2+10,MAXM=1e4+10;
const double eps=1e-6;
int n,m,d[MAXN];
struct edge{
	int next,to,dis;
}e[MAXM<<1];
int head[MAXN],cnt;
double f[MAXN],zg[MAXN][MAXN],ans;
void add(int a,int b,int c){
	e[++cnt].to=b;
	e[cnt].next=head[a];
	e[cnt].dis=c;
	head[a]=cnt;
}
void gauss(){
	for(int i=1;i<=n;i++){
		int r=i;
		for(int k=i;k<=n;k++){
			if(fabs(zg[k][i])>eps){
				r=k;break;
			}
		}
		if(r!=i) swap(zg[r],zg[i]);
		for(int j=n+1;j>=i;j--)
			zg[i][j]/=zg[i][i];
		for(int k=i+1;k<=n;k++)
			for(int j=n+1;j>=i;j--)
				zg[k][j]-=zg[k][i]*zg[i][j];
	}
	for(int i=n-1;i>=1;i--)
		for(int j=i+1;j<=n;j++)
			zg[i][n+1]-=zg[i][j]*zg[j][n+1];
	return ;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>m;
	for(int i=1,u,v,w;i<=m;i++){
		cin>>u>>v>>w;
		add(u,v,w);if(u!=v) add(v,u,w);
		d[u]++;if(u!=v) d[v]++;
	}
	for(int k=1;k<=30;k++){
		memset(zg,0,sizeof zg);
		for(int x=1;x<n;x++){
			int sum=0;
			zg[x][x]=d[x];
			for(int i=head[x];i;i=e[i].next){
				int y=e[i].to;
				if(e[i].dis&(1<<(k-1))) zg[x][y]+=1,zg[x][n+1]++;
				else zg[x][y]-=1;
			} 
		}
		zg[n][n]=-1;
		gauss();
		ans+=zg[1][n+1]*(1<<(k-1));
	}
	cout<<fixed<<setprecision(3)<<ans;
	return 0;
}
posted @ 2025-06-15 15:53  zhangch_qwq  阅读(13)  评论(0)    收藏  举报