题目大意:给出一颗树和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;
}