1. 程式人生 > >【LOJ#10131】暗的鎖鏈

【LOJ#10131】暗的鎖鏈

題目大意:給定一個 N 個點無向圖的一棵生成樹和另外 M 條邊,第一次去掉生成樹中的一條邊,第二次去掉另外 M 條邊中的一條邊,求有多少種情況可以使得給定的無向圖不連通。

題解:首先考慮該生成樹,若新增加一條邊,則會在樹上形成一個環,這時若刪除的樹邊在環上,則只有將環切斷才能使得圖不連通;若刪除的樹邊不在環上,則切斷任意一個其他邊均可。因此,只需要計算出樹上每條邊是否在環中即可。再次分析加邊過程,若在 x,y 兩點加了一條邊,則由 x,y,lca(x,y) 之間的所有邊形成了一個環,將這條樹鏈上的邊權加一即可,由此引出樹上差分做法,即:每個點記錄下該點到父節點之間的邊的環數,只需將 val[x],val[y] 加一,val[lca(x,y)] 減 2 即可,最後 dfs 求出子樹和即可。注:根節點不參與答案貢獻。

程式碼如下

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;

inline int read(){
    int x=0,f=1;char ch;
    do{ch=getchar();if(ch=='-')f=-1;}while(!isdigit(ch));
    do{x=x*10+ch-'0';ch=getchar();}while(isdigit(ch));
    return f*x;
}

struct node{
    int nxt,to;
}e[maxn<<2];
int tot=1,head[maxn],val[maxn];
inline void add_edge(int from,int to){
    e[++tot]=node{head[from],to},head[from]=tot;
}
int n,m,f[maxn][30],dep[maxn];
long long ans;

void dfs(int u,int fa){
    dep[u]=dep[fa]+1,f[u][0]=fa;
    for(int i=1;i<=20;i++)f[u][i]=f[f[u][i-1]][i-1];
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;if(v==fa)continue;
        dfs(v,u);
    }
}

int lca(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    for(int i=20;i>=0;i--)if(dep[f[x][i]]>=dep[y])x=f[x][i];
    if(x==y)return x;
    for(int i=20;i>=0;i--)if(f[x][i]^f[y][i])x=f[x][i],y=f[y][i];
    return f[x][0];
}

void read_and_parse(){
    n=read(),m=read();
    for(int i=1,x,y;i<n;i++){
        x=read(),y=read();
        add_edge(x,y),add_edge(y,x);
    }
    dfs(1,0);
}

void calc(int u,int fa){
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;if(v==fa)continue;
        calc(v,u);
        val[u]+=val[v];
    }
    if(u==1)return;
    else if(!val[u])ans+=m;
    else if(val[u]==1)++ans;
}

void solve(){
    int x,y;
    for(int i=1;i<=m;i++){
        x=read(),y=read();
        ++val[x],++val[y],val[lca(x,y)]-=2;
    }
    calc(1,0);
    printf("%lld\n",ans);
}

int main(){
    read_and_parse();
    solve();
    return 0;
}