UOJ#416. 【APIO2018】铁人两项
原文链接www.cnblogs.com/zhouzhendong/p/UOJ416.html
前言
完了完了SB选手Tarjan写挂。
题解
考虑先Tarjan缩个点双建个圆方树。
然后发现,确定起点和终点后,中间点的可选方案数就是 这条路径上的所有点双 size 之和-2 。
定义原点表示原图中的点,方点表示圆方树中新加入的点。
这个东西可以转化为路径上的方点度数之和减去原点个数。
定义点 x 的权值 d[x] ,当 x 为圆点时 d[x] = -1,否则 d[x] 等于 x 的度数。
设起点终点都是圆点的经过点 x 的路径条数为 c[x],那么点 x 对答案的贡献就是 d[x] * c[x] 。
时间复杂度 $O(n)$。
代码
#include <bits/stdc++.h> #define clr(x) memset(x,0,sizeof (x)) #define For(i,a,b) for (int i=a;i<=b;i++) #define Fod(i,b,a) for (int i=b;i>=a;i--) #define pb(x) push_back(x) #define mp(x,y) make_pair(x,y) #define fi first #define se second #define real __zzd001 #define _SEED_ ('C'+'L'+'Y'+'A'+'K'+'I'+'O'+'I') #define outval(x) printf(#x" = %d\n",x) #define outvec(x) printf("vec "#x" = ");for (auto _v : x)printf("%d ",_v);puts("") #define outtag(x) puts("----------"#x"----------") #define outarr(a,L,R) printf(#a"[%d...%d] = ",L,R);\ For(_v2,L,R)printf("%d ",a[_v2]);puts(""); using namespace std; typedef long long LL; typedef unsigned long long ULL; typedef vector <int> vi; LL read(){ LL x=0,f=0; char ch=getchar(); while (!isdigit(ch)) f|=ch=='-',ch=getchar(); while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); return f?-x:x; } const int N=100005*2; int n,m,k; vector <int> e[N],t[N]; LL ans=0; int dfn[N],low[N],st[N],Time=0,top=0; void Add_Edge(int x,int y){ t[x].pb(y),t[y].pb(x); } int Size; void Tarjan(int x){ Size++; dfn[x]=low[x]=++Time; st[++top]=x; for (auto y : e[x]) if (!dfn[y]){ Tarjan(y); low[x]=min(low[x],low[y]); if (low[y]>=dfn[x]){ Add_Edge(x,++k); do { Add_Edge(st[top],k); } while (st[top--]!=y); } } else low[x]=min(low[x],dfn[y]); } int size[N],d[N]; void dfs(int x,int pre){ size[x]=x<=n; int v=x<=n?-1:(int)t[x].size(); for (auto y : t[x]) if (y!=pre){ dfs(y,x); ans+=2LL*v*size[x]*size[y]; size[x]+=size[y]; } ans+=2LL*v*size[x]*(Size-size[x]); } int main(){ n=read(),m=read(),k=n; For(i,1,m){ int x=read(),y=read(); e[x].pb(y),e[y].pb(x); } For(i,1,n) if (!dfn[i]) Size=0,Tarjan(i),dfs(i,0); cout<<ans<<endl; return 0; }