1. 程式人生 > 實用技巧 >[USACO19JAN]Exercise Route P 題解

[USACO19JAN]Exercise Route P 題解

題目連結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;
}