[USACO19JAN]Exercise Route P 題解
阿新 • • 發佈:2020-11-05
題目連結: https://www.luogu.com.cn/problem/P5203
題意:
給定一棵 \(N\) 樹,和樹外的若干條邊,加起來共有 \(M\) 條邊。求有多少個簡單環上恰有兩條非樹邊。
\((1\le N,M\le2\times10^5)\)
題解:
直接維護這個奇奇怪怪的東西很難做,先轉化一下題意。
經過兩條非樹邊,每條邊直接走是分別有個一環的。
如果這兩個環有公共邊就可以合併起來。
因此題意轉化為:有若干條鏈,求有多少無序對有公共邊。
因為是無序對,所以要先考慮統計的順序,可以對每條鏈統計下面的鏈。
具體就是樹上差分一下,兩端點之和減去 LCA 處的兩倍(畢竟用點來存邊的貢獻)。
不過仍然有重複的,需要提前減掉貢獻:
- 兩條直上直下的路徑從同一個點開始
這樣會在 LCA 下面形成兩次貢獻,而實際貢獻為 \(\frac{n(n-1)}{2}\) ,只要每次減掉當時的 \(n\) 即可(算上這個)。
- 兩條路徑兩次相交
可以用 map
存一下之前有沒有這樣的路徑(有的話 LCA 的兩個在路徑上的兒子會一樣),減掉目前有多少個。
這兩種情況直接按本意模擬比較複雜,分到每次減掉貢獻比較好寫,所以程式碼寫起來就比較簡單,但實際上內部比較複雜。
時間複雜度: \(O(N\log N)\) 。
程式碼:
#include <bits/stdc++.h> #define to e[x][i] using namespace std; typedef long long ll; const int N=2e5+5; int n,m,sum,p[N],q[N],d[N],s[N],dfi[N],dfo[N],f[20][N]; ll ans; vector<int> e[N]; map<pair<int,int>,int> b; void dfs1(int x){ dfi[x]=++sum; for(int i=1;i<20;i++) f[i][x]=f[i-1][f[i-1][x]]; for(int i=0;i<e[x].size();i++) if(to!=f[0][x]) {d[to]=d[x]+1;f[0][to]=x;dfs1(to);} dfo[x]=sum; } inline bool ac(int x,int y) {return dfi[x]<=dfi[y]&&dfo[y]<=dfo[x];} inline int lca(int x,int y){ if(d[x]>d[y]) swap(x,y);if(ac(x,y)) return x; for(int i=19;~i;i--) if(f[i][x]&&!ac(f[i][x],y)) x=f[i][x]; return f[0][x]; } inline int top(int x,int y){ for(int i=19;~i;i--) if(d[f[i][x]]>d[y]) x=f[i][x]; return x; } void dfs2(int x){ for(int i=0;i<e[x].size();i++) if(to!=f[0][x]) {s[to]+=s[x];dfs2(to);} } signed main(){ scanf("%d%d",&n,&m);m-=n-1; for(int i=1,u,v;i<n;i++) {scanf("%d%d",&u,&v);e[u].push_back(v);e[v].push_back(u);} dfs1(1); for(int i=1,x,y,l,u,v;i<=m;i++){ scanf("%d%d",&x,&y);p[i]=x;q[i]=y;l=lca(x,y);u=top(x,l);v=top(y,l); if(l!=x) ans-=++s[u];if(l!=y) ans-=++s[v]; if(l!=x&&l!=y) {if(u>v) swap(u,v);ans-=b[make_pair(u,v)]++;} } dfs2(1); for(int i=1;i<=m;i++) ans+=s[p[i]]+s[q[i]]-2*s[lca(p[i],q[i])]; printf("%lld\n",ans); return 0; }