Luogu P1600 [NOIp提高组2016]天天爱跑步

本文介绍了一种利用LCA(最近公共祖先)和DFS(深度优先搜索)解决特定路径问题的方法。通过将S到T的路径分解并利用等式约束,实现了对路径上节点的高效查询。文章详细介绍了算法思路及其实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


题目描述 传送门


我觉得好难好难啊,看题解我都看不懂(我觉得实现起来有点麻烦)。
总结一下:
首先也许可以想出S=1和T=1的数据的做法,然后想到把S->T路径拆成S->LCA和LCA->T.
然后要想到一个点i可观测到的人一定满足:
S->LCA路径:
depth[i]+w[i]=depth[S]
LCA->T路径:
distance(S,i)=w[i]
depth[S]+depth[i]2depth[LCA]=w[i]
整理得: depth[i]w[i]=depth[T]distance(S,T)
等式右边的都是定值,可以预处理好。
然后两遍DFS分别处理S->LCA和LCA->T的情况,并用桶记录有多少个满足条件的等式右边。
注意到对于一个点如果一条路径没有经过它即使满足等式也不行。

  • 如果那条路径不在以那个点位根的子树,解决方法是ans累加桶的变化量。
  • 如果那条路径在,解决方法是在DFS回溯时减去已这个点为LCA的路径的贡献。

然而这样还会重复算到每条路径的LCA的答案(因为LCA既在S->LCA又在LCA->T)所以最后减去重复的才是答案。


//代码参考dalao的
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=3e5+5,maxm=3e5+5,N=3e5;
int maxd=0;
struct Edge{
    int to,nxt;
    Edge(int a=0,int b=0):to(a),nxt(b){}
}edge[maxm*2];
int n,m; 
int s[maxn],t[maxn],lca[maxn],len[maxn]; 
int cnt=0,h[maxn];
int f[maxn],depth[maxn],son[maxn],size[maxn],top[maxn]; 
int w[maxn],tong[maxn*2],tong2[maxn*2],num[maxn],ans[maxn];
vector<int> v[maxn],v2[maxn],v3[maxn]; 
void addedge(int from,int to){
    edge[++cnt]=Edge(to,h[from]);
    h[from]=cnt;
}
void dfs1(int u,int fa){
    f[u]=fa;
    depth[u]=depth[fa]+1;
    son[u]=0;
    size[u]=1;
    for(int i=h[u];i;i=edge[i].nxt) if(edge[i].to!=fa) {
        int v=edge[i].to;
        dfs1(v,u);
        if(size[v]>size[son[u]]) son[u]=v;
        size[u]+=size[v];
    }
}
void dfs2(int u,int tp){
    top[u]=tp;
    if(size[u]>1) dfs2(son[u],tp);
    for(int i=h[u];i;i=edge[i].nxt) if(edge[i].to!=f[u]&&edge[i].to!=son[u])
        dfs2(edge[i].to,edge[i].to);
}
int get_lca(int x,int y){   //我用树链剖分求的LCA
    int f1=top[x],f2=top[y];
    while(f1!=f2){
            if(depth[f1]<depth[f2]){ 
            swap(x,y);
            swap(f1,f2);
        }
        x=f[f1];
        f1=top[x];
    }
    if(depth[x]<depth[y])
        swap(x,y);
    return y;
} 
void dfss(int u,int fa){
    int now=depth[u]+w[u],x;
    if(now<maxd) x=tong[now];
    for(int i=h[u];i;i=edge[i].nxt)if(edge[i].to!=fa){
        dfss(edge[i].to,u);
    }
    tong[depth[u]]+=num[u]; 
    if(now<maxd) ans[u]=tong[now]-x; 
    for(int i=0;i<v[u].size();i++) tong[v[u][i]]--;
}
void dfst(int u,int fa){
    int now=depth[u]-w[u]+N;
    int x=tong2[now];
    for(int i=h[u];i;i=edge[i].nxt)if(edge[i].to!=fa){
        dfst(edge[i].to,u);
    }
    for(int i=0;i<v2[u].size();i++) tong2[v2[u][i]+N]++;
    ans[u]+=tong2[now]-x;
    for(int i=0;i<v3[u].size();i++) tong2[v3[u][i]+N]--;
}
int main(){
    memset(h,0,sizeof(h));
    memset(num,0,sizeof(num));
    cin>>n>>m;
    for(int i=0;i<n-1;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        addedge(a,b);
        addedge(b,a);
    }
    for(int i=1;i<=n;i++) scanf("%d",&w[i]);
    depth[1]=1;
    son[0]=0;
    dfs1(1,1);
    dfs2(1,1);
    for(int i=1;i<=n;i++) maxd=max(maxd,depth[i]);
    maxd++;
    for(int i=1;i<=m;i++){
        scanf("%d%d",&s[i],&t[i]);
        lca[i]=get_lca(s[i],t[i]);
        len[i]=depth[s[i]]+depth[t[i]]-depth[lca[i]]*2;
        v[lca[i]].push_back(depth[s[i]]);
        num[s[i]]++;
    }
    dfss(1,1);
    for(int i=1;i<=m;i++){
        v2[t[i]].push_back(depth[t[i]]-len[i]);
        v3[lca[i]].push_back(depth[t[i]]-len[i]);
    }
    dfst(1,1);
    for(int i=1;i<=m;i++) if(depth[s[i]]-depth[lca[i]]==w[lca[i]]) ans[lca[i]]--;
    for(int i=1;i<=n;i++) printf("%d ",ans[i]);
    return 0;
}

一开始求LCA还写错了QWQ


NOIp提高组2016 AK

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值