提示:
1. 先考虑暴力情况的 DP 方程
2. 环上的点很少, 而且如果你在环上走只能是连续的一段,也许可以改进一下 DP
代码后详细说明:
#include
using namespace std;
const int maxn = 1e5+1e2;
int n , m;
struct edge { int t , v , re; edge(int t=0 , int v=0 , int re=0):t(t),v(v),re(re){} };
vector g[maxn];
vector<vector<double> > d[maxn];
bool vis[maxn] , inCircle[maxn];
int pre[maxn] , cnt , dic[maxn] , redic[maxn];;
bool dfs(int x)
{
vis[x] = 1;
for(int i=0;iif(e.t == pre[x]) continue;
if(vis[e.t])
{
while(true)
{
dic[++cnt] = x;
redic[x] = cnt;
inCircle[x] = 1;
if(x == e.t) break;
x = pre[x];
}
return true;
}
pre[e.t] = x;
if(dfs(e.t)) return true;
}
return false;
}
double dp(int x , int n1 , int n2)
{
double &res = d[x][n1][n2];
if(res > -1) return res;
res = 0;
int cnt = 0;
for(int i=0;iif(i != n1)
{
edge& e = g[x][i];
if(e.t == dic[n2]) continue;
++cnt;
res += e.v;
if(inCircle[x] == inCircle[e.t]) res += dp(e.t , e.re , n2);
else if(inCircle[x] && !inCircle[e.t]) res += dp(e.t , e.re , 0);
else if(!inCircle[x] && inCircle[e.t]) res += dp(e.t , e.re , redic[e.t]);
}
if(!cnt) return 0;
return res /= cnt;
}
int main(int argc, char *argv[]) {
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
#endif
cin>>n>>m;
for(int i=1 , s , t , v;i<=m;i++)
{
scanf("%d%d%d" ,&s , &t , &v);
assert(s != t);
g[s].push_back(edge(t , v , g[t].size()));
g[t].push_back(edge(s , v , g[s].size()-1));
}
dfs(1);
for(int i=1;i<=n;i++)
{
d[i].resize(g[i].size() + 1);
for(int j=0;j<=g[i].size();j++)
{
d[i][j].resize(cnt + 1);
for(int k=0;k<=cnt;k++) d[i][j][k] = -100;
}
}
double res = 0;
for(int i=1;i<=n;i++) if(inCircle[i]) res += dp(i , g[i].size() , redic[i]); else res += dp(i , g[i].size() , 0);
res /= n;
printf("%.5lf\n" , res);
return 0;
}
DP 方程:
di,j,k走到第 i 个点,不能走此时的第 j 条边,也不能走到环上的第 k 个点的期望距离
如果我们只考虑树上的情况,那么前两个状态就 Okay 了,由于此时可能有环,我们需要纪录每条路径第一次进入环的那个节点是谁,因为我们不能回到它。
一个优化的策略是在走出环之后强制第三维是 0 。
但细心的读者会发现这是个有可能超时的做法, 因为在菊花图中有于转移的时间复杂度是 O(此节点的边数) 所以会退化成 O(n2) 的。但此题数据无心卡菊花图,此算法的运行时间良好。