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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值