题解 P3469 【[POI2008]BLO-Blockade】

本文详细解析了POI2008BLO-Blockade题目,介绍了如何使用Tarjan算法求解割点问题,通过计算删除每个点后图的联通性变化来求解有序点对数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

POI2008 BLO-Blockade


题意:

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


题解:

一道十分适合练习TarjanTarjanTarjan的图论题。

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


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

是割点:还是少了一个点

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

即大小为sizea,sizebsize_a,size_bsizea,sizeb的两个联通块对答案产生2sizeasizeb2 size_a size_b2sizeasizeb的贡献。

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

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

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

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

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

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


考虑TarjanTarjanTarjan如何求联通块大小。

TarjanTarjanTarjan实际上是在dfsdfsdfs生成树上,利用dfndfndfnlowlowlow对返祖边进行标记。

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

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

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

于是可以方便地算出后者的sizesizesize之和sumsumsum,而前者总大小即为n−sum−1n-sum-1nsum1

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

输出时乘以二即可。。。


代码:

#include<cstdio>
#include<algorithm>
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。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值