[SDOI2012] 走迷宫

前言

吐了,这东西能出一万个锅。

题目

洛谷

DarkBZOJ

讲解

随机游走考虑高斯消元,结合强连通分量大小不超过 \(100\) 可以推测是对强连通分量进行高斯消元。

先考虑无解情况:从 \(S\) 走到一个无法到达 \(T\) 的点。

其实并不好做,稍微转化一下:如果存在一个 \(S\) 可以不经过 \(T\) 到达的点,但从 \(T\) 出发在反图上无法到达,即无解。

然后我们缩点,按拓扑序倒序做高斯消元,这样可以保证一个强连通分量连到外面的点的期望已经求出。

而这个拓扑序倒序其实就是我们求出强连通分量的顺序,所以直接做即可。

时间复杂度 \(O(100^3(\frac{n}{100})+m)=O(10^4n+m)\),上限是 \(10^8\),可以过。

代码

自认为比较优美的代码
//12252024832524
#include <bits/stdc++.h>
#define TT template<typename T>
using namespace std;

typedef long long LL;
const int MAXN = 10005;
const int MAXM = 1000005;
int n,m,S,T;
int ID[MAXN];

LL Read()
{
	LL x = 0,f = 1; char c = getchar();
	while(c > '9' || c < '0'){if(c == '-') f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
TT void Put1(T x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}

int head[MAXN],tot,rtot,rhead[MAXN];
struct edge
{
	int v,nxt;
}e[MAXM],re[MAXM];
void Add_Edge(int &t,edge *E,int *hd,int x,int y)
{
	E[++t] = edge{y,hd[x]};
	hd[x] = t;
}

bool xvis[MAXN];
void xinei(int x)
{
	xvis[x] = 1;
	if(x == T) return;
	for(int i = head[x]; i ;i = e[i].nxt) if(!xvis[e[i].v]) xinei(e[i].v);
}
bool nvis[MAXN];
void neixi(int x)
{
	nvis[x] = 1;
	for(int i = rhead[x]; i ;i = re[i].nxt) if(!nvis[re[i].v]) neixi(re[i].v);
}
bool ins[MAXN];
int s[MAXN],tl,dfn[MAXN],low[MAXN],dfntot,scc,bl[MAXN],deg[MAXN];
vector<int> p[MAXN];
void Tarjan(int x)
{
	dfn[x] = low[x] = ++dfntot;
	s[++tl] = x; ins[x] = 1;
	for(int i = head[x],v; i ;i = e[i].nxt)
	{
		v = e[i].v;
		if(!dfn[v]) Tarjan(v),low[x] = Min(low[x],low[v]);
		else if(ins[v]) low[x] = Min(low[x],dfn[v]);
	}
	if(low[x] == dfn[x])
	{
		int v; ++scc;
		do
		{
			v = s[tl--];
			ins[v] = 0;
			bl[v] = scc;
			p[scc].emplace_back(v);
		}while(v^x);
	}
}
double a[105][105],dp[MAXN];
void Gauss(int N)
{
	for(int i = 1;i <= N;++ i)
	{
		int now = i;
		for(int j = i+1;j <= N;++ j) if(Abs(a[j][i]) > Abs(a[now][i])) now = j;
		if(i ^ now) swap(a[i],a[now]);
		for(int j = 1;j <= N;++ j)
		{
			if(i == j) continue;
			double mu = a[j][i] / a[i][i];
			for(int k = i;k <= N+1;++ k) a[j][k] -= mu * a[i][k];
		}
	}
}

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n = Read(); m = Read(); S = Read(); T = Read();
	for(int i = 1,u,v;i <= m;++ i) //之前以为是加边的问题,所以手动实现了函数Add_Edge
	{
		u = Read(),v = Read();
		e[++tot] = edge{v,head[u]}; head[u] = tot;
		re[++rtot] = edge{u,rhead[v]}; rhead[v] = rtot;
		++deg[u];
	}
	xinei(S); neixi(T);
	for(int i = 1;i <= n;++ i) if(xvis[i] && !nvis[i]) {printf("INF\n");return 0;}
	Tarjan(S);
	for(int sc = 1;sc <= scc;++ sc) 
	{
		int N = 0;
		for(auto x : p[sc]) ID[x] = ++N,a[N][N] = 1;
		for(auto x : p[sc])
		{
			if(x^T) a[ID[x]][N+1] = 1;
			else continue;
			for(int i = head[x],v; i ;i = e[i].nxt)
			{
				v = e[i].v;
				if(bl[v] ^ bl[x]) a[ID[x]][N+1] += 1.0 / deg[x] * dp[v];
				else a[ID[x]][ID[v]] += -1.0 / deg[x];
			}
		}
		Gauss(N);
		for(int i = 1;i <= N;++ i) dp[p[sc][i-1]] = a[i][N+1] / a[i][i];
		for(int i = 1;i <= N;++ i)
			for(int j = 1;j <= N+1;++ j)
				a[i][j] = 0;
	}
	printf("%.3f\n",dp[S]);
	return 0;
}
posted @ 2021-10-20 22:42  皮皮刘  阅读(39)  评论(0编辑  收藏  举报