POI2008 BLO-Blockade
题意:
给定一张无向联通图(V,E)(V,E)(V,E),对于每一个点P∈VP\in VP∈V,求当PPP不能被经过时满足u,vu,vu,v不连通的有序点对数。
题解:
一道十分适合练习TarjanTarjanTarjan的图论题。
对于每一个点,考虑将其删除对图会产生什么影响,进而计算答案。
不是割点:~~显然只是少了个点而已,~~答案就是2(n−1)2(n-1)2(n−1)。
是割点:还是少了一个点
删除割点将会导致整个图变成多个联通块。容易发现不同联通块内的点之间不能互相到达。
即大小为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(n−1)+∑i=1k∑j=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-1∑j=1,j̸=iksizej=n−sizei−1
所以上式化为2(n−1)+∑i=1ksizei×(n−sizei−1)2(n-1)+\sum_{i=1}^k size_i \times( n-size_i-1 )2(n−1)+∑i=1ksizei×(n−sizei−1)
不是割点可以认为割掉以后产生一个大小为111的联通块,不必单独处理。
考虑TarjanTarjanTarjan如何求联通块大小。
TarjanTarjanTarjan实际上是在dfsdfsdfs生成树上,利用dfndfndfn和lowlowlow对返祖边进行标记。
所以一个点的子树可以分成两类:存在返祖边或不存在。
对于前者,割掉该点并不影响连通性,所以和祖先算作一个联通块;
对于后者,割掉该点将使得其变为独立的联通块,所以在搜索时顺便计算sizesizesize。
于是可以方便地算出后者的sizesizesize之和sumsumsum,而前者总大小即为n−sum−1n-sum-1n−sum−1。
在搜索时一边累加sumsumsum,一边累加答案,最后加上n−1n-1n−1,得到的是无序点对的个数。
输出时乘以二即可。。。
代码:
#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。。。