P3469 [POI2008]BLO-Blockade
题意:
有一个连通的有向图,求出删除一个点后,不能连通的点对的个数 (\((x,y),(y,x)\) 算两对)
分析:
很明显,既然涉及到环类求点集的题目,很明显是 \(tarjan\) 。
有一个性质:
从一个点集到另一个点集,形成的点对个数(相反算两对) 是:\(2 \times size[x]*size[y]\), 其中 \(size\) 是这个点集的大小
我们进行分类讨论:
\(x\) 不是割点:
断了这个点,对其他点的连通性貌似也没有影响,只是少了这个点关于其他点不能联通的点对。
因此答案就是: \(ans[x]=2 \times (n-1)\)
\(x\) 是割点:
断了这个点,其他一些点也要断了属于是。
我们画出来一张图表现一下:
我们选择断了 \(x\) 点,那么 \(y,z\) 与 \(x\) 上方的节点也要断。
我们枚举 \(x\) 的子树 \(y,z\),分别计算这个集合到其他点的点对,不算其他点到这个集合的点对,避免重复。
从图分析则是:计算 \(y \rightarrow (x,z,father[x])\) ,\(z \rightarrow (x,y,father[x])\) 的点对,因此,则有转移方程:
ans[x]+=size[y]*(n-size[y])
我们发现,这样计算还有些纰漏。我们没有计算从父亲到 \(x\) 子树和 \(x\) 本身失去的点对个数。
我们计算 \(sum=size[y]+size[z]+...\) 为 \(x\) 的子树大小(不包括 \(x\) ),那么 \(n-sum-1\) 则是父亲大小。
最后,我们还要计算 \(x\) 到其他点形成的点对 \(n-1\)。
因此,最后加上:
ans[x]+=(sum+1)*(n-sum-1)+n-1;
最后输出即可。
代码:
//P3469 [POI2008]BLO-Blockade
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+5;
int nxt[N],ver[N],tot,head[N];
int n,m,dfn[N],low[N],cnt,sta[N],top,sd[N],idx;
int sizes[N],cut[N];
ll ans[N];
bool vis[N];
void add(int x,int y){
ver[++tot]=y;nxt[tot]=head[x];head[x]=tot;
}
void tarjan(int x){
dfn[x]=low[x]=++cnt;
sizes[x]=1;vis[x]=1;
int flag=0,sum=0;
for(int i=head[x];i;i=nxt[i]){
int y=ver[i];
if(!dfn[y]){
tarjan(y); sizes[x]+=sizes[y]; low[x]=min(low[x],low[y]);
if(low[y]>=dfn[x]){
ans[x]+=(ll)sizes[y]*(n-sizes[y]); sum+=sizes[y];
flag++;
if(x!=1||flag>1) cut[x]=true;
}
}
else if(vis[y]) low[x]=min(low[x],dfn[y]);
}
if(!cut[x]) ans[x]=2*(n-1);
else ans[x]+=(ll)(n-sum-1)*(sum+1)+n-1;
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y; scanf("%d%d",&x,&y); add(x,y);add(y,x);
}
tarjan(1);
for(int i=1;i<=n;i++) cout<<ans[i]<<endl;
system("pause");
return 0;
}