[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;
}

浙公网安备 33010602011771号