随机游走问题

随机游走指的是在一个点以等可能的概率走向下一个点。

问题1:

Link:https://www.luogu.com.cn/problem/P3232

题意:这道题其实相当于求每条边经过的期望次数。

思路:每条边经过的期望次数不好算,我们想到点经过的期望次数。于是有公式:

f< u,v>=\frac{E(u)}{deg(u)}+ \frac{E(v)}{deg(v)}

这个公式是显而易见的,它的含义是每条边经过的期望次数相当于从这条边的两个端点出发选择这条边的概率。于是我们就成功将这道题转化为求每个点经过的期望概率。而每个点经过的期望概率就是这个点由其直接相邻的点到达的期望。可能有点绕,但写成公式还是比较好理解的。

E(u)=\displaystyle\sum_{e(u,v)\epsilon E}\frac{E(v)}{deg(v)}

需要特别指明的是,1号节点由于是出发点,我们需要+1,以保证初始就经过1号节点这个条件。

初次之外,第n个点期望经过次数应该为0,否则就结束了。这种形式我们就可以写高斯消元了。

高斯消元的板子

void Gauss(int n,double **a,double *b,double *x)
{
	for(int i=1;i<=n;++i)
	{
		int p=i;
		for(int k=i+1;k<=n;++k)if(fabs(a[k][i])>fabs(a[p][i]))p=i;
		if(i!=p)swap(a[i],a[p]),swap(b[i],b[p]);
		for(int k=i+1;k<=n;++k)
		{
			double d=a[k][i]/a[i][i];
			b[k]-=d*b[i];
			for(int j=i;j<=n;++j)a[k][j]-=d*a[i][j];
		} 
	}
	for(int i=n;i>=1;--i)
	{
		for(int j=i+1;j<=n;++j)b[i]-=x[j]*a[i][j];
		x[i]=b[i]/a[i][i];
	}
} 

ac代码

#include<bits/stdc++.h>
using namespace std; 
typedef long long ll;
const int mod=998244353;//1000000007;
const int inf=0x3f3f3f3f;
const int N=1e5+10;
const int M=5e5+10;
const int tt=(int)(log2(N))+1;
ll qcm(ll a,ll b,ll mod)
{
	ll res=1ll;
	while(b)
	{
		if(b&1ll)res=res*a%mod;
		a=a*a%mod;
		b>>=1ll;
	}
	return res;
}
int bsgs(int a, int b, int mod)
{
    map<int, int> mp;
    int cur = 1, t = (int)sqrt(mod) + 1;
    for(int B = 0; B < t; ++B)
	{
		mp[b*cur%mod] = B;
        cur = (ll)cur * a % mod;
    }
    int now = cur;
    for(int A = 1; A <= t; ++A)
	{
        if(mp.find(now)!=mp.end()) return (ll)A * t - mp[now];
        now = (ll)now * cur % mod;
    }
    return -1;
}
void Gauss(int n,double a[][510],double *b,double *x)
{
	for(int i=1;i<=n;++i)
	{
		int p=i;
		for(int k=i+1;k<=n;++k)if(fabs(a[k][i])>fabs(a[p][i]))p=i;
		if(i!=p)swap(a[i],a[p]),swap(b[i],b[p]);
		for(int k=i+1;k<=n;++k)
		{
			double d=a[k][i]/a[i][i];
			b[k]-=d*b[i];
			for(int j=i;j<=n;++j)a[k][j]-=d*a[i][j];
		} 
	}
	for(int i=n;i>=1;--i)
	{
		for(int j=i+1;j<=n;++j)b[i]-=x[j]*a[i][j];
		x[i]=b[i]/a[i][i];
	}
} 
int a[510][510],deg[510];
double dp[510],aa[510][510],p[510];
double dd[125010];
void solve()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		a[x][y]=a[y][x]=i;
		deg[y]++;
		deg[x]++;
	}
	p[1]=-1;
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=n;++j)
		{
			if(i==j)aa[i][j]=-1;
			else if(a[i][j])aa[i][j]=(double)1/deg[j];
		}
	}
	Gauss(n-1,aa,p,dp);
	for(int i=1;i<=n;++i)
	{
		for(int j=i+1;j<=n;++j)
		{
			if(!a[i][j])continue;
			dd[a[i][j]]=dp[i]/deg[i]+dp[j]/deg[j];
		}
	}
	sort(dd+1,dd+m+1);
	double ans=0;
	for(int i=1;i<=m;++i)ans+=dd[i]*(m-i+1);
	printf("%.3lf\n",ans);
}
void init()
{
	
}
int main()
{
	int T=1;
	//init();
	//scanf("%d",&T);
	while(T--)solve();
    return 0;
}

问题2:

给定一棵含n个节点的树,q次询问,每次询问两个点的期望步数。

题目链接没找到2333,将就着看吧

思路:对于期望,我们是可以相加的。也就是说如果z在x—>y的必经路上,那么x—>y的期望步数就等于x—>z和z—>y的期望步数之和。既然这样,我们就可以考察每个点走到其父亲节点的期望步数。

dp[x]=\frac{1+\displaystyle\sum_{y\epsilon son(x)} (dp[x]+dp[y]+1)}{deg[x]}

也就是对于每个点,我们可以选择直接往上走,也可以先走到子节点在往上走。

观察这个式子,我们能得出叶子节点的值为1。

再将上式稍作变形,我们得到

dp[x]=deg[x]+\displaystyle\sum_{y\varepsilon son(x)}dp[y]

观察这个式子,我们猜测:

dp[x]=2*size[x]-1

当树的大小(在这里我定义为树的最大深度)是1时,显然成立,我们假设当树的大小不超过k时也成立,则当树的大小为k+1时,将变形后的式子代入得:

dp[x]=deg[x]+\displaystyle\sum_{y\epsilon son(x)}(2*size[y]-1)

整理得;

dp[x]=1+2*\displaystyle\sum_{y\epsilon son(x)}size[y]

又由于,

size[x]=1+\displaystyle\sum_{y\epsilon son(x)}size[y]

于是我们的猜测成立。

写到这里我们算是解决了一小半的问题了,但是还有个问题在困扰着我们,那就x—>y与y—>x的期望步数不一定相等。最简单的反例是其他都相同,但是x节点是作为叶子结点,y节点下面挂着很多子节点,这样两种走法的期望就不一样了,一定要注意这个陷阱。

仿照刚才的思路,我们设G[i]为从父亲节点x走到i的期望步数。同样有方程:

G[i]=\frac{1+G[x]+G[i]+1+\displaystyle\sum_{y\epsilon son(x),y!=i}(1+dp [y]+G[i])}{deg[x]}

化简得,

G[i]=deg[x]+G[x]+\displaystyle\sum_{y\epsilon son(x),y!=i}dp[y]

将dp的值代入得,

G[i]=2*size[x]+G[x]-2*size[i]

到这一步我们的思路似乎就卡住了,算G的值似乎算不了。但是我们考虑换根,将以x为父亲变成以i为父亲,这样我们就能在G[i]和dp[x]之间构架关系。换根之后的dp[x]就等于原先的G[i],于是我们得到

G[i]=2*(n-size[x])-1

到这里,我们就可以使用树上前缀和,或者变成区间求和等等方法计算 ,当然先算dp再取反也是可行的思路。

留坑,等找到题目再写

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值