开始的时候没有借助圆方树去思考,思路非常混乱,想了很长时间后冷静下来发现这题不就是分类讨论简单题嘛...
题目不难,分两种情况讨论:
设当前点为中间点(圆点)
- 起点从一个儿子的子树进入到中间点后进入到另一个儿子的子树.
- 起点从一个儿子的子树进入到中间点后仍然回到该儿子子树中(相当于上一条的子问题)
- 然后对于儿子(方点)直接累加,父亲(方点)要扣掉当前子树的影响.
code:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define N 200006
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
vector<int>G[N];
ll ans;
int edges,tim,tot,n,m,fr,SIZE;
int hd[N<<1],to[N<<1],nex[N<<1],dfn[N],low[N],S[N],deg[N],size[N];
ll g[N],F[N];
void add(int u,int v)
{
nex[++edges]=hd[u],hd[u]=edges,to[edges]=v;
}
void tarjan(int x)
{
dfn[x]=low[x]=++tim;
S[++fr]=x;
++SIZE;
for(int i=hd[x];i;i=nex[i])
{
int y=to[i];
if(!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
if(low[y]>=dfn[x])
{
++tot;
G[x].push_back(tot);
G[tot].push_back(x);
++deg[tot];
for(int p=0;p!=y;--fr)
{
p=S[fr];
++deg[tot];
G[tot].push_back(p);
G[p].push_back(tot);
}
}
}
else
{
low[x]=min(low[x],dfn[y]);
}
}
}
void dfs(int x,int ff)
{
size[x]=(x<=n);
for(int i=0;i<G[x].size();++i)
{
int y=G[x][i];
if(y==ff) continue;
dfs(y,x);
size[x]+=size[y];
}
}
void solve(int x,int ff)
{
if(x<=n)
{
int cu=0;
for(int i=0;i<G[x].size();++i)
{
int y=G[x][i];
if(y==ff) cu=SIZE-size[x];
else cu=size[y];
ans+=1ll*cu*(SIZE-cu-1);
}
if(ff)
{
ans+=g[ff]-1ll*size[x]*SIZE;
ans-=1ll*size[x]*(SIZE-2*size[x]);
}
}
else
{
for(int i=0;i<G[x].size();++i)
{
int y=G[x][i];
if(y==ff) continue;
F[x]+=1ll*size[y]*(size[x]-size[y]);
g[x]+=1ll*size[y]*(SIZE-size[y]);
}
g[x]+=1ll*size[x]*(SIZE-size[x]);
ans+=1ll*F[x];
}
for(int i=0;i<G[x].size();++i)
if(G[x][i]!=ff) solve(G[x][i],x);
}
int main()
{
// setIO("input");
scanf("%d%d",&n,&m);
tot=n;
for(int i=1;i<=m;++i)
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y),add(y,x);
}
for(int i=1;i<=n;++i)
{
if(!dfn[i])
{
tim=SIZE=fr=0;
tarjan(i);
dfs(i,0);
solve(i,0);
}
}
printf("%lld\n",ans);
return 0;
}