1. 程式人生 > >POJ - 3417 Network (LCA+樹上差分)

POJ - 3417 Network (LCA+樹上差分)

poj wap 計數 ems 連通 討論 等於 pro inline

題目傳送門:POJ - 3417 Network

題目大意:

存在一棵n個結點的樹,加入m條新邊,現在要讓這個圖不連通,你可以切斷兩條邊,要求切斷一條原邊,一條新邊,求切割的方案數。

分析:

加入m條新邊,假設加入新邊(u,v),那麽u-->lca(u,v)-->v-->u形成一個環,此時可以切斷新邊,和環上任意的一條原邊就可以使圖不連通。

可以發現若加入一條新邊,給環上的計數1,表示該邊被一個環覆蓋,樹上有些邊會被多個環覆蓋,此時可以分情況討論。

1、若該邊被覆蓋次數是0,則斷掉該邊後圖已經滿足不連通條件了,此時可以斷掉任意一條新邊,圖依舊不連通,可該邊以產生m種新方案。

2、若該邊被覆蓋次數是1,則斷掉該邊後必須斷掉與之對應的新邊才能使圖不連通,可以產生1種新方案。

3、若該邊被覆蓋次數大於等於2,則不能在斷掉一條新邊和一條原邊的情況下使圖不連通,所以不能產生新方案。

所以該題目可以轉化為,每次加入新邊後,給形成對應的環上的邊+1,最後統計樹上所有邊的權值(樹上差分),若權值是0方案數+m,權值是1方案數+1

可以求出最後結果

代碼

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAX=100009
; const int M=20; int n,m,a,b; int head[MAX],cnt=0; int up[MAX][M]; int deep[MAX]; int de[MAX]; struct edge{ int next,to; }edge[MAX*2]; inline void add(int u,int v) { edge[cnt].to=v; edge[cnt].next=head[u]; head[u]=cnt++; } void dfs(int u) //
遍歷數,求出結點的深度和父親 { for(int i=head[u];i!=-1;i=edge[i].next) { int to=edge[i].to; if(to==up[u][0])continue; deep[to]=deep[u]+1; up[to][0]=u; dfs(to); } } void init() { memset(up,-1,sizeof(up)); deep[1]=1; dfs(1); for(int j=1;(1<<j)<=n;j++) //倍增 for(int i=1;i<=n;i++) if(~up[i][j-1]) up[i][j]=up[up[i][j-1]][j-1]; } int lca(int a,int b) { if(deep[a]<deep[b]) swap(a,b); int d=deep[a]-deep[b]; for(int i=0;i<M;i++) if(d&(1<<i)) a=up[a][i]; if(a==b)return a; for(int i=M-1;i>=0;i--) { if(up[a][i]!=up[b][i]) a=up[a][i],b=up[b][i]; } return up[a][0]; } void dfs_ans(int u) { for(int i=head[u];i!=-1;i=edge[i].next) { int to=edge[i].to; if(to==up[u][0])break; dfs_ans(to); de[u]=de[u]+de[to]; } } int main() { memset(head,-1,sizeof(head)); memset(de,0,sizeof(de)); scanf("%d%d",&n,&m); for(int i=1;i<n;i++) { scanf("%d%d",&a,&b); add(a,b); add(b,a); } init(); for(int i=0;i<m;i++) { scanf("%d%d",&a,&b); de[a]++; //樹上差分 de[b]++; de[lca(a,b)]-=2; } dfs_ans(1); int res=0; for(int i=2;i<=n;i++) { if(de[i]==0) res+=m; if(de[i]==1) res++; } printf("%d\n",res); return 0; }

POJ - 3417 Network (LCA+樹上差分)