测试地址:铁人两项
做法:本题需要用到圆方树+树上统计。
首先有个结论:一个点双连通分量内的任意三个点,不管以什么顺序排列,都存在一条简单路径按顺序经过这三个点。
那么如果我们选定了路径的起点和终点,可以作为中间点的点,就是它们之间的路径所经过的所有点双连通分量中的点(除了两端)。于是我们就把圆方树建出来,方点的点权设为对应点双中的点数,为了去重,圆点的点权设为,那么可以作为中间点的点数就等于两个点在圆方树路径上的点权和。于是我们很自然地转化成求每个点的贡献,也就是求经过该点的路径数量,这就是NOIP难度的树上统计了,那么我们就完成了这一题,时间复杂度为。
我傻逼的地方:假圆方树毁一生……经过兜兜转转,总算找到了真正正确的圆方树写法,以前的做法都是假的……我真的是醉了…..
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,first[200010]={0},tot=0,firstt[200010]={0},totpbc;
int low[200010],dfn[200010],tim=0,st[200010],top=0;
bool vis[200010]={0};
ll ans=0,val[200010],sum=0;
struct edge
{
int v,next;
}e[400010],t[200010];
void insert(int a,int b)
{
e[++tot].v=b;
e[tot].next=first[a];
first[a]=tot;
}
void insertt(int a,int b)
{
t[++tot].v=b;
t[tot].next=firstt[a];
firstt[a]=tot;
}
void tarjan(int v,int fa)
{
sum++;
val[v]=0-1;
low[v]=dfn[v]=++tim;
st[++top]=v;
vis[v]=1;
int now=top;
for(int i=first[v];i;i=e[i].next)
if (e[i].v!=fa)
{
if (!vis[e[i].v])
{
tarjan(e[i].v,v);
low[v]=min(low[v],low[e[i].v]);
if (low[e[i].v]>=dfn[v])
{
totpbc++;
val[totpbc]=1;
insertt(v,totpbc);
do
{
insertt(totpbc,st[top]);
val[totpbc]++;
}while(st[top--]!=e[i].v);
}
}
else low[v]=min(low[v],dfn[e[i].v]);
}
}
int solve(int v,bool type)
{
ll siz=type;
for(int i=firstt[v];i;i=t[i].next)
{
ll now=solve(t[i].v,!type);
ans+=now*(sum-now)*val[v];
siz+=now;
}
ans+=(sum-siz)*siz*val[v];
if (type) ans+=(sum-1ll)*val[v];
return siz;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
insert(a,b),insert(b,a);
}
tot=0,totpbc=n;
for(int i=1;i<=n;i++)
if (!vis[i])
{
sum=0;
tarjan(i,0);
solve(i,1);
}
printf("%lld",ans);
return 0;
}