ahoi2005 lane 航线规划

6 篇文章 0 订阅
3 篇文章 0 订阅

题目链接

题解

对于这样一类删边+询问的题目,可以很快就想到“正难则反”这一思路,因此可以离线做,先将要删的边删掉,再反着处理每一个询问,于是删边就被处理成了加边。
然而,即使这样,仍然很麻烦,因为这是一个图,可能有环,非常麻烦。
可以考虑运用dfs树,先利用dfs造出一棵树,然后再将没包含在树上的边加入更新。
对于关键路径,非常显然,就是树上的边了。
现在考虑如何更新。
每加入一条边,就在树上形成了一个环,将这个环上的边全缩成一个点,就可以了,对于缩点,很快就可以想到利用并查集。
询问时,不断利用并查集向上跳,一直到LCA,这样虽然不是正解,但是已经可以过掉这题了。

#include<cstdio>
#include<cctype>
#include<vector>
#include<algorithm>
using namespace std;
const int M=30005;
vector<int>edge[M];
int n,m,q,a[40005],b[40005],c[40005];
bool mark[M];
int fa[M],dep[M];
void rec(int x){
    mark[x]=1;
    for(int i=0;i<edge[x].size();i++){
        if(mark[edge[x][i]]) continue;
        fa[edge[x][i]]=x;
        dep[edge[x][i]]=dep[x]+1;
        rec(edge[x][i]);
    }
}
int par[M],ans[40005];
int get(int x){
    if(par[x]!=x) par[x]=get(par[x]);
    return par[x];
}
int Query(int a,int b){
    int ans=0;
    a=get(a);b=get(b);
    while(a!=b){
        if(dep[a]<dep[b]) swap(a,b);
        a=get(fa[a]);
        ans++; 
    }
    return ans;
}
void update(int a,int b){
    a=get(a);
    b=get(b);
    while(a!=b){
        if(dep[a]<dep[b]) swap(a,b);
        par[a]=fa[a];
        a=get(a);
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        edge[a].push_back(b);
        edge[b].push_back(a);
    }
    for(int i=1;i<=n;i++)
        par[i]=i;
    for(int i=1;i<=n;i++)
        sort(edge[i].begin(),edge[i].end());
    while(true){
        scanf("%d",&a[q]);
        if(a[q]==-1) break;
        scanf("%d%d",&b[q],&c[q]);
        if(a[q]==0){
            edge[b[q]].erase(lower_bound(edge[b[q]].begin(),edge[b[q]].end(),c[q]));
            edge[c[q]].erase(lower_bound(edge[c[q]].begin(),edge[c[q]].end(),b[q]));
        }
        q++;
    }
    rec(1);
    fa[1]=1;
    for(int i=1;i<=n;i++){
        for(int j=0;j<edge[i].size();j++){
            if(fa[i]==edge[i][j]||fa[edge[i][j]]==i) continue;
            update(edge[i][j],i);
        }
    }
    for(int i=q-1;i>=0;i--){
        if(a[i]==0){
            update(b[i],c[i]);
        }else{
            ans[i]=Query(b[i],c[i]);
        }
    }
    for(int i=0;i<q;i++)
        if(a[i]) printf("%d\n",ans[i]);
    return 0;
}

正解还是非常机智的。
考虑一棵树,如果把一条树边删掉,那么它的子树深度就会全部减少一。根据两个点的深度以及两个点的LCA就可以求出这两个点之间的树边的条数。
首先利用dfs序将问题转换到序列上,因为在dfs序中,子树是一段连续的区间。
于是可以利用一个树状数组来维护,这棵树状数组是区间更新,单点询问的。
其余的和上面是一样的。

#include<cstdio>
#include<cctype>
#include<vector>
#include<algorithm>
#define lowbit(x) (x&-x)
using namespace std;
const int M=30005;
vector<int>edge[M];
int n,m,q,a[40005],b[40005],c[40005];
bool mark[M];
int fa[M][20],dep[M],dfn[M],dfc,mx[M];
void rec(int x){
    mark[x]=1;
    dfn[x]=++dfc;
    for(int i=0;i<edge[x].size();i++){
        if(mark[edge[x][i]]) continue;
        fa[edge[x][i]][0]=x;
        dep[edge[x][i]]=dep[x]+1;
        rec(edge[x][i]);
    }
    mx[x]=dfc;
}
int par[M],ans[40005];
int get(int x){
    if(par[x]!=x) par[x]=get(par[x]);
    return par[x];
}
int bit[M];
void add(int x,int w){
    for(int i=x;i>0;i-=lowbit(i))
        bit[i]+=w;
}
int sum(int x){
    int res=0;
    for(int i=x;i<=dfc;i+=lowbit(i))
        res+=bit[i];
    return res;
}
int up(int a,int step){
    for(int i=0;i<20;i++){
        if(step&(1<<i)) a=fa[a][i];
    }
    return a;
}
int LCA(int a,int b){
    if(dep[a]<dep[b]) swap(a,b);
    a=up(a,dep[a]-dep[b]);
    if(a!=b){
        for(int i=19;i>=0;i--){
            if(fa[a][i]!=fa[b][i]) a=fa[a][i],b=fa[b][i];
        }
        a=fa[a][0];
    }
    return a;
}
int Query(int a,int b){
    return sum(dfn[a])+sum(dfn[b])-sum(dfn[LCA(a,b)])*2;
}
void del(int a,int rt){
    a=get(a);
    while(dep[a]>dep[rt]){
        add(dfn[a]-1,1);
        add(mx[a],-1);
        par[a]=fa[a][0];
        a=get(a);
    }
}
void update(int a,int b){
    int lca=get(LCA(a,b));
    del(a,lca);
    del(b,lca); 
}
inline void Rd(int&res){
    res=0;char c;
    while(c=getchar(),!isdigit(c));
    do res=(res<<1)+(res<<3)+(c^48);
    while(c=getchar(),isdigit(c));
}
int main(){
    scanf("%d%d%d",&n,&m,&q);
    for(int i=0;i<m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        edge[a].push_back(b);
        edge[b].push_back(a);
    }
    for(int i=1;i<=n;i++)
        par[i]=i;
    for(int i=1;i<=n;i++)
        sort(edge[i].begin(),edge[i].end());
    for(int i=0;i<q;i++){
        Rd(a[i]);Rd(b[i]);Rd(c[i]);
        if(a[i]==0){
            edge[b[i]].erase(lower_bound(edge[b[i]].begin(),edge[b[i]].end(),c[i]));
            edge[c[i]].erase(lower_bound(edge[c[i]].begin(),edge[c[i]].end(),b[i]));
        }
    }
    rec(1);
    for(int i=1;i<=n;i++){
        add(dfn[i],dep[i]);
        add(dfn[i]-1,-dep[i]);
    }
    for(int j=1;j<20;j++)
        for(int i=1;i<=n;i++)
            fa[i][j]=fa[fa[i][j-1]][j-1];
    for(int i=1;i<=n;i++){
        for(int j=0;j<edge[i].size();j++){
            if(fa[i][0]==edge[i][j]||fa[edge[i][j]][0]==i) continue;
            update(edge[i][j],i);
        }
    }
    for(int i=q-1;i>=0;i--){
        if(a[i]==0){
            update(b[i],c[i]);
        }else{
            ans[i]=Query(b[i],c[i]);
        }
    }
    for(int i=0;i<q;i++)
        if(a[i]) printf("%d\n",ans[i]);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值