P5203 [USACO19JAN]Exercise Route-樹上的前綴和差分

題目大意:給出一顆樹和m條邊,統計兩條有交集的邊有多少種情況。

這樣的題目我當然是抄題解啦。抄題解還花了好長時間才弄明白(自認爲)。

題解說明
滿足要求的跑步路線必須恰好包含兩條“非標準”道路,於是我們研究兩條“非標準”道路在什麼情況下能構成環。我們稱一條“非標準”道路兩邊的點在樹上的路徑爲這條“非標準”道路在樹上的path。我們發現,兩條“非標準”道路能構成一個環當且僅當他們的path有重邊。如下圖,1和2之間可以構成環,因爲他們之間有一條重邊(紅色邊),而1和3則不行,因爲他們之間沒有重邊。事實上,如果兩條“非標準”道路的path有重邊,我們只需要取兩條path除重邊外的部分首尾相接就能構成環。

我的理解:這是題解上的說的,找樹上的重邊問題,方法我很難理解。
將其簡化理解,變成再數軸上找有重邊的兩條路,用桶T[]統計每條路徑左端點出現的位置,並維護前綴和ST[],那麼對路徑a來說,f(a)=ST[a.r]-ST[a.l-1]就是這個區間和其有重複路徑的條數,這個條數裏面包含了a線段本身,所以最後結果的時候要減去。
那麼主要有以下3種情況
1.線段ab不相交 ,f(a),f(b)相互統計不上
2.ab相交或包含,左端點不重合
  例如 a1 6 b 2 3,那麼f(a)差分的時候統計上了b,而f(b)統計不上a,不會重複統計。
         a1 6 b 2 7,那麼f(a)差分的時候統計上了b,而f(b)統計不上a,不會重複統計。 
3.ab左端點重合
 那麼f(a)差分的時候統計上了b,而f(b)統計上f(a)。這樣會重複統計。
結合開頭的會重複統計自己,加上情況3的重複,正好是 sum(T[i]).這個可以再預處理時候減去,也可以最後統一減去。
再擴展的樹上的情況,我們將路徑收縮到深度大的點上,將路徑權值轉化爲點權,我們將一條路徑(u,v)拆分兩部分(u-lca)(v-lca),找到lca後還要找到lca的兒子,然後通過樹上的前綴和差分進行類似數軸上的統計。
樹上還有一種重複的情況就是兩天路徑的lca相投,那麼左右兩側都將統計爲重複路徑,因此再設一個map,找出所有lca相同的點,也就是對飲的map[u,v]>1時候纔出現多的,重複統計了sum(map[u,v]-1)次。

參考代碼:抄題解上的,簡單修改下。

//tj
#include <bits/stdc++.h>
#define ll long long
#define mk make_pair
#define pii pair<int,int>
using namespace std;
inline int read() {
    int f=1,x=0;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
const int MAXN=2e5+5;
int n,m;
struct EDGE{
    int next,to;
}edge[MAXN<<1];
int head[MAXN],tot;
inline void addEdge(int u,int v) {
    edge[++tot].next=head[u];
    edge[tot].to=v;
    head[u]=tot;
}
int fa[MAXN][30],dep[MAXN];
void dfs1(int u) {
    for(int i=1;i<=20;++i) {
        fa[u][i]=fa[fa[u][i-1]][i-1];
    }
    dep[u]=dep[fa[u][0]]+1;
    for(int i=head[u];i;i=edge[i].next) {
        int v=edge[i].to;
        if(v==fa[u][0]) continue;
        fa[v][0]=u; dfs1(v);
    }
}
int lca(int u,int v) {
    if(dep[u]<dep[v]) swap(u,v);
    for(int i=20;i>=0;--i) {
        if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
    }
    if(u==v) return u;
    for(int i=20;i>=0;--i) {
        if(fa[u][i]!=fa[v][i]) {
            u=fa[u][i];
            v=fa[v][i];
        }
    }
    return fa[u][0];
}
int gettop(int bot,int top) {//將路徑值轉化爲節點值,每條路徑轉化爲其下方的葉節點的對應的值, 
    if(top==bot) return -1;//gettop就找到這條鏈最上方的節點,也就是公共祖先的在這個鏈上的兒子節點。 
    for(int i=20;i>=0;--i) {
        if(dep[fa[bot][i]]>dep[top]) bot=fa[bot][i];
    }
    return bot;
}
pii node[MAXN];
map<pii,ll> Tpi;
ll T[MAXN],num[MAXN],anc[MAXN];
/*T是一個桶,存某個點重複算的數量*/
void dfs2(int u,int curNum) {//這個處理樹的上的前綴和,然後再用差分差分,經過【a,b】段的節點數就是sum【b】-sum【a-1】,前面預處理出來了 
    num[u]=curNum;
    for(int i=head[u];i;i=edge[i].next) {
        int v=edge[i].to;
        if(v!=fa[u][0]) dfs2(v,curNum+T[v]);
    }
}
int main() {
    n=read(); m=read();
    for(int i=1;i<=n-1;++i) {
        int u,v; u=read(); v=read();
        addEdge(u,v); addEdge(v,u);
    }
    dfs1(1);
    ll ans=0;
    for(int i=1;i<=(m-(n-1));++i) {
        int u,v;
        u=node[i].first=read();
        v=node[i].second=read();
        anc[i]=lca(u,v);
        int uu=gettop(u,anc[i]);//例如已經有 
        if(uu!=-1) {
            T[uu]++;//   ans-=T[uu];//開頭在A_i 前的區間的數量從答案裏減去 
        }
        int vv=gettop(v,anc[i]);
        if(vv!=-1) {    
            T[vv]++;// ans-=T[vv];
        }
        if(uu!=-1 && vv!=-1) {
            if(uu>vv) swap(uu,vv);
           // ans-=Tpi[mk(uu,vv)];//當兩個點的lca相同時候,左右兩側的分支都會計算,重複,需要減去。 
            Tpi[mk(uu,vv)]++;
        }
    }
    dfs2(1,0);/*樹上前綴和*/
    for(int i=1;i<=(m-(n-1));++i) {//差分 
        ans+=num[node[i].first]+num[node[i].second]-2*num[anc[i]];
    }
    for(int i=1;i<=n;i++)
		ans-=(T[i]+1)*T[i]/2;
	map<pii,ll>::iterator it;
	for(it=Tpi.begin();it!=Tpi.end();it++){
		ll tmp=it->second;
		ans-=(tmp-1)*tmp/2;
	}
    cout<<ans<<endl;
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章