Description
在一棵基环树(或者树)任选点,不能走走过的点,求走到不能走的期望长度
(nle 10^5),环上的点 (le 20)
Solution
树的部分可以考虑用换根法来解决
定义 (f_i) 为以 (i) 为起点,在子树里面的期望路径长度
这里有一个小误区:这个定义转移的时候应该除以 (du_i-1) (状态定义)
正确性用期望线性性能给
另一个部分用换根做就行
struct task1{
int n, m, cnt=1, du[N], head[N];
double f[N], g[N], ans, d[N];
struct node {
int to, dis, nxt;
} e[N << 1];
inline void add(int u, int v, int w) {
e[++cnt].to = v;
++du[v];
e[cnt].nxt = head[u];
e[cnt].dis = w;
return head[u] = cnt, void();
}
inline void dfs1(int x, int fa) {
for (reg int i = head[x]; i; i = e[i].nxt) {
int t = e[i].to;
if (t == fa)
continue;
dfs1(t, x);
d[x] += f[t] + e[i].dis;
} f[x] = d[x] / max(1,du[x]-1);
return;
}
inline void dfs2(int x, int fa) {
for (reg int i = head[x]; i; i = e[i].nxt) {
int t = e[i].to;
if (t == fa)
continue;
d[t] += (d[x] - f[t] - e[i].dis) / max(1ll, du[x] - 1) + e[i].dis;
dfs2(t, x);
}
return;
}
inline void main()
{
for(reg int i=1,u,v,w;i<=m;++i) u = read(), v = read(), w = read(), add(u, v, w), add(v, u, w);
dfs1(1, 0);
dfs2(1, 0);
For(i, 1, n) ans += 1.0 * d[i] / du[i];
printf("%.6lf
", ans / n);
return ;
}
}T1;
然后考虑环套树的情况
基于 (Island) 和 (Roads In The Kindom) 中的套路:
把一棵基环树看成当前子树的根,然后分别处理掉,再对环上的点处理一下
先算出来 (f_i) ,然后考虑环上的点的 (g_i) 值,可以往左侧走,也可以往右侧走
可以暴力处理环上点的 (g_i) 值,然后再反向更新回去子树里面的 (g_i)
(我实现能力可能是真的不行,写了一周,调了好久好久)
struct task2{
int cnt,rt, du[N], head[N];
double t[N<<1],f[N], g[N], ans,res[N], d[N];
struct node {
int to, dis, nxt;
} e[N << 1];
inline void add(int u, int v, int w) {
e[++cnt].to = v;
e[cnt].nxt = head[u];
e[cnt].dis = w;
return head[u] = cnt, void();
}
pair<int,int> h[N<<1];
int num,vis[N];
inline bool find(int x,int fa,int pre)
{
if(vis[x]==1)
{
h[++num]=make_pair(x,e[pre].dis); vis[x]=3;
return 1;
} vis[x]=1;
for(int i=head[x];i;i=e[i].nxt)
{
if(i==(pre^1)) continue;
int t=e[i].to;
if(find(t,x,i))
{
if(vis[t]==3&&num!=1) return 0;
if(vis[x]!=3) h[++num]=make_pair(x,e[pre].dis);
return 1;
}
if(num) return 0;
}return 0;
}
inline void dfs1(int x,int fa)
{
for(reg int i=head[x];i;i=e[i].nxt)
{
int t=e[i].to; if(t==fa||vis[t]) continue;
dfs1(t,x); d[x]+=f[t]+e[i].dis; du[x]++;
}
if(du[x]) f[x]=d[x]/du[x]; if(x!=rt) du[x]++;
return ;
}
inline void dfs2(int x,int fa)
{
for(reg int i=head[x];i;i=e[i].nxt)
{
int t=e[i].to; if(t==fa||vis[t]) continue;
double tmp=(res[x]*du[x]-e[i].dis-f[t])/max(1,du[x]-1);
res[t]=(f[t]*(du[t]-1)+tmp+e[i].dis)/du[t];
dfs2(t,x);
} return ;
}
inline void main()
{
cnt=1; for(reg int i=1,u,v,w;i<=m;++i) u = read(), v = read(), w = read(), add(u, v, w), add(v, u, w);
find(1,0,0);
memset(vis,0,sizeof(vis)); For(i,1,num) vis[h[i].first]=1;
For(i,1,num) h[i+num]=h[i],rt=h[i].first,dfs1(h[i].first,0);
For(i,1,num)
{
int nd=h[i].second;
double np=1;
For(j,i+1,num+i-1)
{
if(j!=num+i-1) g[h[i].first]+=(nd+f[h[j].first]*du[h[j].first]/(du[h[j].first]+1))*np;
else g[h[i].first]+=(nd+f[h[j].first])*np;
nd=h[j].second; np/=(du[h[j].first]+1);
}
nd=h[i+num-1].second; np=1;
Down(j,i+1,num+i-1)
{
if(j!=i+1) g[h[i].first]+=(nd+f[h[j].first]*du[h[j].first]/(du[h[j].first]+1))*np;
else g[h[i].first]+=(nd+f[h[j].first])*np;
nd=h[j-1].second; np/=(du[h[j].first]+1);
}
}
For(i,1,num) res[h[i].first]=(f[h[i].first]*du[h[i].first]+g[h[i].first])/(du[h[i].first]+2),du[h[i].first]+=2,dfs2(h[i].first,0);
For(i,1,n) ans+=res[i];
printf("%.6lf
",ans/n);
return ;
}
}T2;
Review
(1.) 本题的最大收获就是 (g_i) 的转移一定要想清楚再写,改来改去浪费时间
其实环的处理思路并不复杂,要不是说我把 (g_i) 想乱了,就是个水题……
(2.) 更新自己找环的方法:
inline bool find(int x,int fa,int pre)
{
if(vis[x]==1)
{
h[++num]=make_pair(x,e[pre].dis); vis[x]=3;
return 1;
} vis[x]=1;
for(int i=head[x];i;i=e[i].nxt)
{
if(i==(pre^1)) continue;
int t=e[i].to;
if(find(t,x,i))
{
if(vis[t]==3&&num!=1) return 0;
if(vis[x]!=3) h[++num]=make_pair(x,e[pre].dis);
return 1;
}
if(num) return 0;
}return 0;
}
改的地方主要是判断了个 (vis[t]==3) 否则在祖先那里会错……
(3.) 正确学习期望线性性,这里主要是在暴力处理环那部分
期望长度应该是每个边乘上概率加和,而不是路径长度乘上概率……
(想出来环的那个可以 (O(len^2)) 做就真的不难……)