[圆方树] Luogu P4630 Duathlon 铁人两项
题目描述
比特镇的路网由 mm 条双向道路连接的 nn 个交叉路口组成。
最近,比特镇获得了一场铁人两项锦标赛的主办权。这场比赛共有两段赛程:选手先完成一段长跑赛程,然后骑自行车完成第二段赛程。
比赛的路线要按照如下方法规划:
- 先选择三个两两互不相同的路口 s, cs,c和 ff,分别作为比赛的起点、切换点(运动员在长跑到达这个点后,骑自行车前往终点)、终点。
- 选择一条从 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 }