正题
题目链接:https://www.luogu.com.cn/problem/P4630
题目大意
nnn个点mmm条边的一张无向图,求有多少对三元组(s,c,f)(s,c,f)(s,c,f)满足s≠f≠ts\neq f\neq ts=f=t且存在一条从sss到fff的简单路径经过ccc
解题思路
一个比较显然的结论是在一个点双中的三个点(a,b,c)(a,b,c)(a,b,c)那么必然存在一条aaa到bbb的简单路径经过ccc。因为一定存在两条不交的a−>ca->ca−>c和c−>bc->bc−>b的路径,那么如果一条a−>ca->ca−>c和c−>bc->bc−>b的路径交了,那么另一条就一定不交。
然后从一个点双出来后就不能再回到这个点双了,所以我们可以考虑在圆方树上做这个问题。
设定义圆点的权值为−1-1−1,方点的权值为连接的圆点数量,这样我们在圆方树上走的时候就可以固定经过进入和离开这个点双的点了。
然后问题就变为了求每条圆点之间路径的点权和的和。
用树形dpdpdp搞就好了,时间复杂度O(n)O(n)O(n)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<stack>
using namespace std;
const int N=2e5+10;
int n,m,num,cnt,dfc,w[N];
int low[N],dfn[N],siz[N];
vector<int> G[N],T[N];
stack<int> s;
long long ans;
void tarjan(int x){dfn[x]=low[x]=++dfc;w[x]=-1;s.push(x);num++;for(int y:T[x])if(!dfn[y]){tarjan(y);low[x]=min(low[x],low[y]);if(dfn[x]==low[y]){++cnt;int k;do{k=s.top();G[cnt].push_back(k);G[k].push_back(cnt);w[cnt]++;s.pop();}while(k!=y);G[cnt].push_back(x);G[x].push_back(cnt);w[cnt]++;}}else low[x]=min(low[x],dfn[y]);return;
}
void solve(int x,int fa){siz[x]=(x<=n);for(int y:G[x]){if(y==fa)continue;solve(y,x);ans+=2ll*siz[y]*siz[x]*w[x];siz[x]+=siz[y];}ans+=2ll*siz[x]*(num-siz[x])*w[x];return;
}
int main()
{scanf("%d%d",&n,&m);for(int i=1;i<=m;i++){int x,y;scanf("%d%d",&x,&y);T[x].push_back(y);T[y].push_back(x);}cnt=n;for(int i=1;i<=n;i++)if(!dfn[i]){num=0;tarjan(i);solve(i,0);}printf("%lld\n",ans);return 0;
}