题解 P3469 【[POI2008]BLO-Blockade】

POI2008 BLO-Blockade


题意:

​ 给定一张无向联通图 ( V , E ) (V,E) (V,E),对于每一个点 P ∈ V P\in V PV,求当 P P P不能被经过时满足 u , v u,v u,v不连通的有序点对数。


题解:

一道十分适合练习 T a r j a n Tarjan Tarjan的图论题。

对于每一个点,考虑将其删除对图会产生什么影响,进而计算答案。


不是割点:~~显然只是少了个点而已,~~答案就是 2 ( n − 1 ) 2(n-1) 2(n1)

是割点:还是少了一个点

删除割点将会导致整个图变成多个联通块。容易发现不同联通块内的点之间不能互相到达。

即大小为 s i z e a , s i z e b size_a,size_b sizea,sizeb的两个联通块对答案产生 2 s i z e a s i z e b 2 size_a size_b 2sizeasizeb的贡献。

那么设全图有 n n n个点,删除该割点产生 k k k个联通块,则答案就是联通块两两配对产生的贡献之和。

2 ( n − 1 ) + ∑ i = 1 k ∑ j = 1 , j ≠ i k s i z e i × s i z e j 2(n-1)+\sum_{i=1}^k \sum_{j=1,j\neq i}^{k}size_i \times size_j 2(n1)+i=1kj=1,j̸=iksizei×sizej

这里需要 O ( n 2 ) O(n^2) O(n2)卷起来,遇到菊花图就凉了。。。

考虑优化,显然 ∑ j = 1 , j ≠ i k s i z e j = n − s i z e i − 1 \sum_{j=1,j\neq i}^k size_j =n-size_i-1 j=1,j̸=iksizej=nsizei1

所以上式化为 2 ( n − 1 ) + ∑ i = 1 k s i z e i × ( n − s i z e i − 1 ) 2(n-1)+\sum_{i=1}^k size_i \times( n-size_i-1 ) 2(n1)+i=1ksizei×(nsizei1)

不是割点可以认为割掉以后产生一个大小为 1 1 1的联通块,不必单独处理。


考虑 T a r j a n Tarjan Tarjan如何求联通块大小。

T a r j a n Tarjan Tarjan实际上是在 d f s dfs dfs生成树上,利用 d f n dfn dfn l o w low low对返祖边进行标记。

所以一个点的子树可以分成两类:存在返祖边或不存在。

对于前者,割掉该点并不影响连通性,所以和祖先算作一个联通块;

对于后者,割掉该点将使得其变为独立的联通块,所以在搜索时顺便计算 s i z e size size

于是可以方便地算出后者的 s i z e size size之和 s u m sum sum,而前者总大小即为 n − s u m − 1 n-sum-1 nsum1

在搜索时一边累加 s u m sum sum,一边累加答案,最后加上 n − 1 n-1 n1,得到的是无序点对的个数。

输出时乘以二即可。。。


代码:

#include
#include
using std::freopen;
using std::fread;
using std::scanf;
using std::printf;
using std::min;

char gc()
{
	static char buf[1<<18],*p1=buf,*p2=buf;
	if(p1==p2)
	{
		p2=(p1=buf)+fread(buf,1,1<<18,stdin);
		if(p1==p2)return EOF;
	}
	return *p1++;
}
template<typename _Tp>
void read(_Tp& x)
{
	x=0;
	char c=gc();
	while(c<'0'||c>'9')c=gc();
	while(c>='0'&&c<='9')
	{
		x=(x<<1)+(x<<3)+(c^48);
		c=gc();
	}
}
template<typename _Tp>
void write(_Tp x)
{
	if(x>=10)write(x/10);
	putchar((x%10)^48);
}

const int N=100005,M=500005;

int n,m;

int head[N],dfn[N],low[N];
int size[N];
long long ans[N];

struct Edge
{
	int next,to;
};
Edge E[M<<1];


int tot;
void add(int u,int v)
{
	E[++tot].next=head[u];
	E[tot].to=v;
	head[u]=tot;
}

int cnt;
void tarjan(int u)
{
	dfn[u]=low[u]=++cnt;
	long long sum=0;
	size[u]=1;
	for(int i=head[u];i;i=E[i].next)
	{
		int v=E[i].to;
		if(!dfn[v])
		{
			tarjan(v,u);
			size[u]+=size[v];
			if(low[v]>=dfn[u])//判断不存在返祖边的情况
			{
				ans[u]+=size[v]*sum;//计算这一棵子树的贡献
				sum+=size[v];
			}
			low[u]=min(low[u],low[v]);
		}
		else
		{
			low[u]=min(low[u],dfn[v]);
		}
	}
	ans[u]+=(n-sum-1)*sum;//别忘了计算剩下的点产生的贡献!
	ans[u]+=n-1;
}

int main()
{
	read(n),read(m);
	for(int i=0;i<m;++i)
	{
		int u,v;
		read(u),read(v);
		add(u,v);
		add(v,u);
	}
	tarjan(1);
	for(int i=1;i<=n;++i)
	{
		write(ans[i]*2);//之前求出无序点对,这里乘2
		putchar('\n');
	}
}

P.S. 这题被我校ZJOI2018 rk2神仙拿来出模拟赛,结果数据出现负数导致几乎所有原本A掉这题的人爆50。。。

你可能感兴趣的:(题解)