1. 程式人生 > >[POI2008]BLO-Blockade(tarjan割點)

[POI2008]BLO-Blockade(tarjan割點)

題目連結:https://www.luogu.org/problemnew/show/P3469

 

題意:在n個點m條邊的無向圖,求刪掉一個點後,有多少個有序(x,y)由連通到不連通

 

思路:

分兩種情況:

1.點不為割點,答案就是(n-1)*2(這個點到其他的點,其他的點到這個點)

2.點是割點,那麼圖被分成一些部分,答案就是每個部分點的個數與其他部分的個數乘積的和,再加上(n-1)*2(即上面的)

舉個例子:

 

比如刪掉割點3時

每個部分與其他點的乘積的和:12,也可以這樣計算:

集合(1)    *   集合(2)  =1

集合(1,2)  *   集合(4)=2

集合(1,2,4)*   集合(5)=3  這裡只算了一邊,所以答案就是 6*2

怎麼實現?在搜尋中加上去就行,看程式碼:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
struct
node{ int next,to; }edge[maxn*10]; ll head[maxn],low[maxn],dfn[maxn]; ll ans[maxn],size[maxn];//ans為答案,size[i]儲存以i為根子樹的大小 int n,m,cnt,tot; void add(int u,int v) { edge[cnt].to=v; edge[cnt].next=head[u]; head[u]=cnt++; } void tarjan(int u) { low[u]=dfn[u]=++tot; ll z
=0;//記錄已求出割點後集合的大小,相當於上面列子左邊的集合 size[u]=1;//初始 for(int i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].to; if(!dfn[v]) { tarjan(v); size[u]+=size[v];//沒遍歷過當然要加上子樹的大小,來求出以u為根子樹的大小 low[u]=min(low[u],low[v]); if(low[v]>=dfn[u])//v為割點 { ans[u]+=(ll)z*size[v];//模擬上面例子乘的過程 z+=size[v];//記得集合變大 } } else low[u]=min(low[u],dfn[v]); } ans[u]+=(ll)z*(n-1-z);//不要忘了,這是最後一步(不只要具體用處,個人理解是算不是割點時的答案) } int main() { cnt=tot=0; memset(head,-1,sizeof(head)); memset(low,0,sizeof(low)); memset(dfn,0,sizeof(dfn)); memset(ans,0,sizeof(ans)); memset(size,0,sizeof(size)); int x,y; scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { scanf("%d%d",&x,&y); add(x,y); add(y,x); } tarjan(1); for(int i=1;i<=n;i++) printf("%lld\n",(ans[i]+n-1)<<1); return 0; }
View Code