[圆方树] Luogu P4630 Duathlon 铁人两项

题目描述

比特镇的路网由 mm 条双向道路连接的 nn 个交叉路口组成。

最近,比特镇获得了一场铁人两项锦标赛的主办权。这场比赛共有两段赛程:选手先完成一段长跑赛程,然后骑自行车完成第二段赛程。

比赛的路线要按照如下方法规划:

  1. 先选择三个两两互不相同的路口 s, cs,c和 ff,分别作为比赛的起点、切换点(运动员在长跑到达这个点后,骑自行车前往终点)、终点。
  2. 选择一条从 ss出发,经过 cc最终到达 ff的路径。考虑到安全因素,选择的路径经过同一个点至多一次。

在规划路径之前,镇长想请你帮忙计算,总共有多少种不同的选取 s, cs,c和 ff的方案,使得在第 22步中至少能设计出一条满足要求的路径。

题解

  • 把圆方树建出来,在树中任意枚举两个圆点作为s和f,然后考虑c有多少种选法。

  • 令每个圆点的权值为-1,每个方点的权值为点双大小,那么选法就是两点路径的权值和

  • 也就是说我们要求圆方树上n^2条圆点到圆点的路径的权值和,很容易想到计算每个点被算了多少次

代码

 1 #include <cstdio>
 2 #include <iostream>
 3 #define N 400010
 4 using namespace std;
 5 int n,m,tot,cnt,num,sum,head[N],last[N],stack[N],sz[N],val[N],dfn[N],low[N];
 6 long long ans;
 7 struct edge {int to,from;}e[N],E[N];
 8 void insert(int x,int y)
 9 {
10     e[++cnt].to=y,e[cnt].from=head[x],head[x]=cnt;
11     e[++cnt].to=x,e[cnt].from=head[y],head[y]=cnt;
12 }
13 void ins(int x,int y){ E[++num].to=y,E[num].from=last[x],last[x]=num; }
14 void tarjan(int x)
15 {
16     dfn[x]=low[x]=++dfn[0],stack[++stack[0]]=x,sz[x]=1,val[x]=-1;
17     for (int i=head[x],l;i;i=e[i].from)
18         if (!dfn[e[i].to])
19         {
20             tarjan(e[i].to),low[x]=min(low[x],low[e[i].to]);
21             if (low[e[i].to]>=dfn[x])
22             {
23                 ins(x,++tot),val[tot]=1,l=0;
24                 do{ l=stack[stack[0]--],ins(tot,l),sz[tot]+=sz[l],++val[tot]; }while (l!=e[i].to);
25                 sz[x]+=sz[tot];
26             }
27         }
28         else low[x]=min(low[x],dfn[e[i].to]);
29 }
30 void dfs(int x)
31 {
32     if (x<=n) ans+=1ll*(sum-1)*val[x];
33     ans+=1ll*(sum-sz[x])*sz[x]*val[x];
34     for (int i=last[x];i;i=E[i].from) ans+=1ll*(sum-sz[E[i].to])*sz[E[i].to]*val[x],dfs(E[i].to); 
35 }
36 int main()
37 {
38     scanf("%d%d",&n,&m),tot=n;
39     for (int x,y;m;m--) scanf("%d%d",&x,&y),insert(x,y);
40     for (int i=1;i<=n;i++) if (!dfn[i]) tarjan(i),sum=sz[i],dfs(i);
41     printf("%lld",ans);
42  } 
原文地址:https://www.cnblogs.com/Comfortable/p/11281783.html