1. 程式人生 > >長樂爆零之旅 day5 蘋果樹

長樂爆零之旅 day5 蘋果樹

這一題是書上差分和lca,可是沒看出來,一開始直接暴力了。 題目描述: 小 B 是一個辛勤的農民,他家裡種了一棵很大的蘋果樹。 這棵蘋果樹可以看作一張 n 個點 n-1 條邊的無向連通圖,小 B 覺得這顆蘋果樹很脆弱,因為只要剪斷任意一條邊,蘋果樹就不連通了,於是他給蘋果樹新加了 m 條邊。現在這顆蘋果樹就不像是一棵樹了,成了一張 n 個點 n+m-1 條邊的無向連通圖,小 Q是小 B 的好朋友,他覺得這棵樹依然很脆弱,他告訴小 B,自己只要破壞兩條邊,一條是原樹中的邊,一條是小 B 新加的邊,就能使蘋果樹不連通。 小 B 覺得小 Q 說得很不可思議,於是他找來了擅長程式設計的你,希望你告訴他,按照小B 的破壞規則,有多少種不同的方案使得蘋果樹不連通。 注意:兩種方案不同當且僅當一條邊在第一種方案中被刪除了但在第二種方案中沒有被 刪除。 輸入格式: 第一行兩個正整數 n,m,表示蘋果樹的點數和後面新加的邊的個數。 接下來 n-1 行,每行兩個正整數 u,v,表示(u,v)之間有一條樹邊。 接下來 m 行,每行兩個正整數 u,v,表示(u,v)之間有一條小 B 新加的邊。 輸出格式: 輸出一行一個整數,表示有多少種不同的方案。 輸入樣例: 4 1 1 2 2 3 1 4 3 4 輸出樣例: 3 在這裡插入圖片描述

**題解:**這一題打死我也看不出來是樹上差分,那些dalao真是太厲害了。 我們先講講為什麼要用樹上差分(不會樹上差分的話我部落格還沒寫,如果寫了連結會放在這個位置),我們先不看新加的邊,如果只是原來的那幾條邊的話,無論斷哪一條都會成立,接下來看看新加的邊,我們可以發現(然而我當時並沒有發現 )如果加了一條邊那麼它起到的作用將與一條鏈,舉個栗子:如果我們在x,y這邊加一條邊,那麼這條邊相當於x到y原樹上的鏈的作用(兩點在樹上只有唯一 一條鏈就不用我說了吧),這樣子我們在加完所有的邊後,我們發現原本樹上的邊起到的作用可能會被不止一條新加的邊替代,那麼我們可以發現,如果這一條邊的作用沒有任何一條新邊代替(意味著這一條邊斷了,整個圖就不連通了),那麼無論另一條我們選哪一條都會不連通,那麼方案數就有m種;如果一條邊的作用剛好被另一條新邊替代,那麼很明顯只要把這兩條都刪了就不連通了,那麼方案數是1;如果有一條邊作用被兩條新邊(或者以上)替代的話,無論怎麼刪除都會聯通,所以這個方案數為0。 接下來我們只要統計一下所有的原邊的作用被多少新邊所替代就ojbk了,下面講怎麼統計。 我們定義一個much陣列,much[i]表示第i個點到自己父親的那一條邊的作用被多少新邊替代(因為父親只有一個,所以這樣的邊也只有唯一 一條),然後我們在讀入新邊的時候樹上差分統計就好了。下面上程式碼:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<queue>
using namespace std;
int head[600100],Next[600100],ver[600100],much[300100],size=0,t,f[300100][20],deep[300100],n,m,vist[300100];//much陣列存對應邊的作用被多少條邊替代
//f陣列存倍增的祖先,即f[i][j]表示i這個節點第2^j個祖先;deep存深度;vist存訪問 
queue <int> q;//倍增預處理用的 
void add
(int x,int y)//邊表常規操作 { size++; Next[size]=head[x]; head[x]=size; ver[size]=y; return ; } void bfs()//我們預處理倍增 { deep[1]=1; q.push(1); while(q.size()) { int x=q.front(); q.pop(); for(int i=head[x];i;i=Next[i]) { int y=ver[i]; if(deep[y]!=0) continue; deep[y]=deep[x]+1;//深度確定 f[y][0]=x;//dp的初始值 for(int j=1;j<=t;j++)//開始dp操作 f[y][j]=f[f[y][j-1]][j-1]; q.push(y); } } return ; } int lca(int x,int y)//詢問兩個點的lca { if(deep[x]>deep[y])//我們確保y的深度比x大 swap(x,y); for(int i=t;i>=0;i--)//把y不斷往上提 if(deep[f[y][i]]>=deep[x]) y=f[y][i]; if(x==y)//如果x和y相等,即x是y的祖先,直接返回x return x; for(int i=t;i>=0;i--)//我們現在確定x,y同一深度後同時往上提 if(f[y][i]!=f[x][i])//只要兩個的祖先不同就可以提 { y=f[y][i]; x=f[x][i]; } return f[x][0];//我們可以確保x,y一定為lca的兒子,直接返回父親 } void dfs(int x)//我們把差分統計一下 { vist[x]=1; for(int i=head[x];i;i=Next[i]) { int y=ver[i]; if(vist[y]==1) continue; dfs(y); much[x]+=much[y];//在回溯時(確保兒子已經計算完了)自加上兒子(差分的常規操作) } } int main() { long long ans=0; scanf("%d %d",&n,&m); t=(int)(log(n)/log(2)+1);//先計算一下log 2 n,後面倍增dp和上提要用 for(int i=1;i<n;i++) { int x,y; scanf("%d %d",&x,&y);//建立樹 add(x,y); add(y,x); } bfs(); for(int i=1;i<=m;i++) { int x,y; scanf("%d %d",&x,&y);//加入新邊 much[lca(x,y)]-=2;//在lca上-2(-2的原因是因為兩個後代各自+1,所以祖先要-2) much[x]++;//後代++ much[y]++; } dfs(1);//dfs統計差分結果 for (int i=2;i<=n;++i)//迴圈判斷,much[1]指根到自己父親的,這一條邊其實是不存在的不過我們為了程式碼好打,同一,所以虛擬了這個邊,記得不要加 if (much[i]==1) //如果有一條邊代替 ans++; else if (!much[i]) //如果有零條邊代替 ans+=m; printf("%lld",ans);//給波答案 return 0; }

唉,還有兩天國慶就gg了,傷心,藍過。