[SDOI2012]走迷宫
题目
点这里看题目。
分析
首先考虑输出
其次考虑
其中
由于可能存在环形转移,所以我们用高斯消元,
接着考虑
每个强连通分量的大小不超过100。
于是我们考虑用它来作文章。先用
所以我们可以按照强连通分量的拓扑序(缩点之后的拓扑序)倒着做,每次用高斯消元解决一个强连通分量内部的
设每个强连通分量的大小为
代码
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
const double eps = 1e-7;
const int MAXN = 1e4 + 5, MAXM = 1e6 + 5, MAXS = 105;
template<typename _T>
void read( _T &x )
{
x = 0;char s = getchar();int f = 1;
while( s > '9' || s < '0' ){if( s == '-' ) f = -1; s = getchar();}
while( s >= '0' && s <= '9' ){x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar();}
x *= f;
}
template<typename _T>
void write( _T x )
{
if( x < 0 ){ putchar( '-' ); x = ( ~ x ) + 1; }
if( 9 < x ){ write( x / 10 ); }
putchar( x % 10 + '0' );
}
template<typename _T>
_T MIN( const _T a, const _T b )
{
return a < b ? a : b;
}
template<typename _T>
_T ABS( const _T a )
{
return a < 0 ? -a : a;
}
struct edge
{
int to, nxt;
}Graph[MAXM];
vector<int> SCC[MAXN];
double A[MAXS][MAXS];
double E[MAXS], f[MAXN];
int q[MAXN], sta[MAXN], top;
int pos[MAXN], seq[MAXS];
int deg[MAXN], head[MAXN], DFN[MAXN], LOW[MAXN], bel[MAXN];
int N, M, S, T, cnt, ID, tot, siz;
bool vis[MAXN], arr[MAXS];
bool equal( const double a, const double b = 0 ) { return ABS( a - b ) < eps; }
void addEdge( const int from, const int to )
{
Graph[++ cnt].to = to, Graph[cnt].nxt = head[from];
head[from] = cnt, deg[from] ++;
}
bool chk()
{
int h = 1, t = 0;
q[++ t] = S;
while( h <= t )
{
int u = q[h ++];
for( int i = head[u], v ; i ; i = Graph[i].nxt )
if( ! vis[v = Graph[i].to] ) vis[v] = true, q[++ t] = v;
}
bool flag = vis[T];
for( int i = 0 ; i <= N ; i ++ ) vis[i] = false;
return flag;
}
void Tarjan( const int u )
{
DFN[u] = LOW[u] = ++ ID;
vis[sta[++ top] = u] = true;
for( int i = head[u], v ; i ; i = Graph[i].nxt )
{
v = Graph[i].to;
if( ! DFN[v] ) Tarjan( v ), LOW[u] = MIN( LOW[u], LOW[v] );
else if( vis[v] ) LOW[u] = MIN( LOW[u], DFN[v] );
}
if( LOW[u] == DFN[u] )
{
int v; tot ++;
do vis[v = sta[top --]] = false, bel[v] = tot, SCC[tot].push_back( v ); while( v ^ u );
}
}
void Gauss()
{
double tmp;
int opt = 1, indx, lst;
for( int i = 1 ; i <= siz && opt <= siz ; lst = i ++, opt ++ )
{
indx = -1;
for( int j = i ; j <= siz ; j ++ )
if( ! equal( A[j][opt] ) && ( indx == -1 || ABS( A[indx][opt] ) < ABS( A[j][opt] ) ) )
indx = j;
if( ! ( ~ indx ) ) { i --; continue; }
std :: swap( A[indx], A[i] );
tmp = A[i][opt];
for( int j = 1 ; j <= siz + 1 ; j ++ ) A[i][j] /= tmp;
for( int j = i + 1 ; j <= siz ; j ++ )
if( ! equal( A[j][opt] ) )
{
tmp = A[j][opt];
for( int k = 1 ; k <= siz + 1 ; k ++ ) A[j][k] -= A[i][k] * tmp;
}
}
for( int i = lst ; i ; i -- )
{
indx = -1;
for( int j = 1 ; j <= siz ; j ++ ) if( equal( A[i][j], 1 ) ) { indx = j; break; }
if( ! ( ~ indx ) ) continue;
E[indx] = A[i][siz + 1];
for( int j = 1 ; j < i ; j ++ ) A[j][siz + 1] -= E[indx] * A[j][indx], A[j][indx] = 0;
}
}
int main()
{
read( N ), read( M ), read( S ), read( T );
for( int i = 1, fr, to ; i <= M ; i ++ ) read( fr ), read( to ), addEdge( fr, to );
if( ! chk() ) { puts( "INF" ); return 0; }
//检查连通性,当然也可以Tarjan的DFN检查
Tarjan( S );
for( int i = 1, v ; i <= tot ; i ++ )
//Tarjan的SCC编号正好就是倒着的拓扑序
{
siz = 0;
for( int j = 0 ; j < SCC[i].size() ; j ++ )
arr[i] |= SCC[i][j] == T, seq[pos[SCC[i][j]] = ++ siz] = SCC[i][j];
//将SCC的点离散到连续正整数上来
for( int j = 0 ; j < SCC[i].size() ; j ++ )
for( int k = head[SCC[i][j]] ; k ; k = Graph[k].nxt )
if( bel[v = Graph[k].to] ^ i ) arr[i] |= arr[bel[v]];
//用DP检查是否可以走到T
if( ! arr[i] ) { puts( "INF" ); return 0; }
for( int i = 1 ; i <= siz ; i ++ )
for( int j = 1 ; j <= siz + 1 ; j ++ )
A[i][j] = 0;
for( int j = 0, u, p ; j < SCC[i].size() ; j ++ )
{
p = pos[u = SCC[i][j]];
A[p][p] = 1;
if( u == T ) continue;
A[p][siz + 1] = 1;
for( int k = head[u] ; k ; k = Graph[k].nxt )
{
if( bel[v = Graph[k].to] ^ i ) A[p][siz + 1] += 1.0 / deg[u] * f[v];
//如果已知,直接加入到常数项里面
else A[p][pos[v]] -= 1.0 / deg[u];
}
}
Gauss();
for( int i = 1 ; i <= siz ; i ++ ) f[seq[i]] = E[i];
}
printf( "%.3lf\n", f[S] );
return 0;
}