Prob.6[割点和割边/Tarjan]洛谷P3469 [POI2008]BLO-Blockade Upd:2020.3.4

传送门:https://www.luogu.com.cn/problem/P3469

我们分析题意:首先,对于完整的原图,任意两点连通,有总共的方案数:n*(n-1)

我们来分析要“阻断”的点,设这个点为i

(1)如果i不是割点,那么删去他以后,原图的n-1个点仍然存在连通性,应该减少了2*(n-1)对关系

(2)如果i是割点(设存在k个割点{s1,s2,s3,......sk}),“隔离”之后假设出现了n个连通块,设t[i]表示第i个连通块的元素个数

那么减少的关系应当是所有的连通块两两相乘加和。

同时,我们在求割点的过程中,需要调用Tarjan算法。在递归dfs每一个元素的时候,我们可以找到计算出来low[y]>=dfn[i]的所有点,以这样的点y

为根的子树在隔离i之后会形成一个连通块(因为subtree(y)不存在一条反向边,连接向i的祖先,在删掉i之后必定会形成一个连通块),只需要统计这样的

y的子树的大小即可。然后每次找到一个y,贡献为: t[y]*(n-t[y])

在跑完tarjan之后,我们判断它是否为割点,如果是,还要加上(n-Sum-1)*(Sum+1)(这是不对于成连通块的剩余部分,和i以及独立的部分的贡献)+(n-1)(这是对于i点到剩余的所有点)

如果不是,那么加上2*(n-1)即可

AC Code:

#include<cstdio>
using namespace std;
const int N=500500;
struct node{
int from;
int to;
int next;
}e[N<<1];
int head[N],num,n,m,dfn[N],low[N],root,size[N],cnt;
long long ans[N];
bool cut[N];
inline int min(int x,int y)
{
return x<y?x:y;
}
void addedge(int from,int to)
{
e[++num].from=from;
e[num].to=to;
e[num].next=head[from];
head[from]=num;
}
void tarjan(int x)
{
size[x]=1;
dfn[x]=low[x]=++cnt;
int y,flag=0,sum=0;
for(int i=head[x];i;i=e[i].next)
{
y=e[i].to;
if(!dfn[y])
{
tarjan(y);
size[x]+=size[y];
low[x]=min(low[x],low[y]);
if(low[y]>=dfn[x])
{
sum+=size[y];
flag++;
ans[x]+=(long long)size[y]*(n-size[y]);
if( x != root || flag>1 ) cut[x]=true;
}
}
else low[x]=min(low[x],dfn[y]);
}
if(cut[x])
ans[x]+=(long long)(n-sum-1)*(sum+1)+(n-1);
else
ans[x]=2*(n-1);
return;
}
int main()
{
int x,y;
num=1;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
if(x==y) continue;
addedge(x,y);
addedge(y,x);
}
for(int i=1;i<=n;i++)
{
if(!dfn[i])
{
root=i;
tarjan(i);
}
}
for(int i=1;i<=n;i++)
printf("%lld\n",ans[i]);
return 0;
}

posted @ 2020-03-04 18:06  会飞的字符串  阅读(138)  评论(0编辑  收藏  举报