bzoj2707

Description

Morenan被困在了一个迷宫里。迷宫可以视为N个点M条边的有向图,其中Morenan处于起点S,迷宫的终点设为T。可惜的是,Morenan非常的脑小,他只会从一个点出发随机沿着一条从该点出发的有向边,到达另一个点。这样,Morenan走的步数可能很长,也可能是无限,更可能到不了终点。若到不了终点,则步数视为无穷大。但你必须想方设法求出Morenan所走步数的期望值。

Input

第1行4个整数,N,M,S,T
第[2, M+1]行每行两个整数o1, o2,表示有一条从o1到o2的边。

Output

一个浮点数,保留小数点3位,为步数的期望值。若期望值为无穷大,则输出"INF"。
【样例输入1】
6 6 1 6
1 2
1 3
2 4
3 5
4 6
5 6
【样例输出1】
3.000
【样例输入2】
9 12 1 9
1 2
2 3
3 1
3 4
3 7
4 5
5 6
6 4
6 7
7 8
8 9
9 7
【样例输出2】
9.500
【样例输入3】
2 0 1 2
【样例输出3】
INF
【数据范围】
测试点
N
M
Hint
[1, 6]
<=10
<=100
 
[7, 12]
<=200
<=10000
 
[13, 20]
<=10000
<=1000000
保证强连通分量的大小不超过100
 
 
另外,均匀分布着40%的数据,图中没有环,也没有自环




首先考虑输出INF的情况。

明显只有s和t不能互相通达或者从s出发可能走到某个岔道之后无法走到t的时候输出INF。

然后是解决如何求s到t的期望步数。

这个明显可以列方程来解。

设点i的到达t的期望步数为ans[i],由定义可知ans[t]=t。

再设count[i]表是以i为起点的有向边的个数。

根据期望值的概念,ans[i]=sigma(ans[v]/count[i])+1 (存在边(i,v))

直接移项高斯消元搞好了。

这样可以通过60%的数据,而且对于大数据有两组INF的,可以得到70分。

大数据如何解决呢?

注意题中提示每个强连通分量大小不超过100,100这个数字是可以高斯消元的。

考虑强连通分量,很容易发现,如果没有环的话,可以按照拓扑序直接算,不需要列方程。

为什么需要列方程呢,因为存在环的话,方程前面自己本身的系数不为1。

什么项中会含有自己呢,只有跟自己在同一个强连通分量里的项。

所以可以把方程分成一个一个强连通分量来按拓扑序求。

这样时间上就可以通过了。

我的程序在本地测数据会有最后几个测试点会超过1s……总计时间6.8s左右……在bzoj上9.4s左右勉强跑完……


program bzoj2707;
const
  eps=1e-7;
type
  st=array [0..10001] of boolean;
  ty1=array [0..10001] of longint;
  ty2=array [0..1000001] of longint;
  equation=array [0..201] of double;
var
  n,m,s,t,i,j,k,u,v,tot,all:longint;
  dd,dfn,low,stack,form,num,roo,root,beg:ty1;
  ans:array [0..10001] of double;
  pre,next,point,nex,poi:ty2;
  o1,o2,fin,yes:st;
  map:array [0..201] of longint;
  mat:array [0..201] of equation;
  con:array [0..201] of double;
  o:double;

procedure swap (var a,b:double);inline;
var
  i:double;
begin
  i:=a;
  a:=b;
  b:=i;
end;

procedure dfs (now:longint;var o:st;var root:ty1;var point,next:ty2);
var
  i:longint;
begin
  o[now]:=true;
  i:=root[now];
  while i<>0 do
    begin
      if not o[point[i]] then
        dfs(point[i],o,root,point,next);
      i:=next[i];
    end;
end;

procedure calc (who:longint);
var
  tot,i,j,k,count:longint;
begin
  tot:=0;
  i:=beg[who];
  while i<>0 do
    begin
      inc(tot);
      map[tot]:=i;
      num[i]:=tot;
      k:=roo[i];
      while k<>0 do
        begin
          dec(dd[form[poi[k]]]);
          if (not fin[form[poi[k]]])and(dd[form[poi[k]]]=0) then
            begin
              fin[form[poi[k]]]:=true;
              inc(all);
              stack[all]:=form[poi[k]];
            end;
          k:=nex[k];
        end;
      i:=pre[i];
    end;
  for i:=1 to tot do
    begin
      fillchar(mat[i],sizeof(mat[i]),0);
      mat[i,i]:=1;
      if map[i]=t then
        begin
          con[i]:=0;
          continue;
        end
                  else
        con[i]:=1;
      count:=0;
      k:=root[map[i]];
      while k<>0 do
        begin
          inc(count);
          k:=next[k];
        end;
      k:=root[map[i]];
      while k<>0 do
        begin
          if form[point[k]]=who then
            mat[i,num[point[k]]]:=mat[i,num[point[k]]]-1/count
                                else
            con[i]:=con[i]+ans[point[k]]/count;
          k:=next[k];
        end;
    end;
  for i:=1 to tot-1 do
    begin
      if abs(mat[i,i])<eps then
        for j:=i+1 to tot do
          if abs(mat[j,i])>eps then
            begin
              mat[0]:=mat[i];
              mat[i]:=mat[j];
              mat[j]:=mat[0];
              swap(con[i],con[j]);
            end;
      for j:=i+1 to tot do
        if abs(mat[j,i])>eps then
          begin
            o:=mat[j,i]/mat[i,i];
            con[j]:=con[j]-con[i]*o;
            for k:=i to tot do
              mat[j,k]:=mat[j,k]-mat[i,k]*o;
          end;
    end;
  for i:=tot downto 1 do
    begin
      o:=con[i];
      for j:=i+1 to tot do
        o:=o-mat[i,j]*ans[map[j]];
      ans[map[i]]:=o/mat[i,i];
    end;
end;

procedure tarjan (now:longint);
var
  i:longint;
begin
  inc(all);
  stack[all]:=now;
  yes[now]:=true;
  dfn[now]:=all;
  low[now]:=all;
  i:=root[now];
  while (now<>t)and(i<>0) do
    begin
      if not yes[point[i]] then
        begin
          tarjan(point[i]);
          if low[now]>low[point[i]] then
            low[now]:=low[point[i]];
        end
                           else
      if form[point[i]]=0 then
        if low[now]>dfn[point[i]] then
          low[now]:=dfn[point[i]];
      i:=next[i];
    end;
  if dfn[now]=low[now] then
    begin
      inc(tot);
      repeat
        i:=stack[all];
        dec(all);
        form[i]:=tot;
        pre[i]:=beg[tot];
        beg[tot]:=i;
      until i=now;
    end;
end;

begin
  read(n,m,s,t);
  if s=t then
    begin
      writeln(0);
      exit;
    end;
  for i:=1 to m do
    begin
      read(u,v);
      point[i]:=v;
      next[i]:=root[u];
      root[u]:=i;
      poi[i]:=u;
      nex[i]:=roo[v];
      roo[v]:=i;
    end;
  dfs(s,o1,root,point,next);
  dfs(t,o2,roo,poi,nex);
  if not o2[s] then
    begin
      writeln('INF');
      exit;
    end;
  for i:=1 to n do
    if o1[i] and (not o2[i]) then
      begin
        writeln('INF');
        exit;
      end;
  for i:=1 to n do
    if o1[i] and o2[i] then
      if not yes[i] then
        begin
          all:=0;
          tarjan(i);
        end;
  for i:=1 to n do
    begin
      k:=root[i];
      while k<>0 do
        begin
          if form[point[k]]<>form[i] then
            inc(dd[form[i]]);
          k:=next[k];
        end;
    end;
  all:=1;
  stack[1]:=form[t];
  fin[form[t]]:=true;
  i:=1;
  while i<=all do
    begin
      calc(stack[i]);
      inc(i);
    end;
  writeln(ans[s]:0:3);
end.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值