【BZOJ 1969】【AHOI 2005】LANE 航线规划【离线、hash、并查集、树链剖分、线段树】

Description

星际空间站的Samuel II巨型计算机经过长期探测,已经锁定了Samuel星系中许多星球的空间坐标,并对这些星球从1开始编号1、2、3……。 一些先遣飞船已经出发,在星球之间开辟探险航线。 探险航线是双向的,例如从1号星球到3号星球开辟探险航线,那么从3号星球到1号星球也可以使用这条航线。 例如下图所示: 在5个星球之间,有5条探险航线。 A、B两星球之间,如果某条航线不存在,就无法从A星球抵达B星球,我们则称这条航线为关键航线。
这里写图片描述
显然上图中,1号与5号星球之间的关键航线有1条:即为4-5航线。 然而,在宇宙中一些未知的磁暴和行星的冲撞,使得已有的某些航线被破坏,随着越来越多的航线被破坏,探险飞船又不能及时回复这些航线,可见两个星球之间的关键航线会越来越多。 假设在上图中,航线4-2(从4号星球到2号星球)被破坏。此时,1号与5号星球之间的关键航线就有3条:1-3,3-4,4-5。 小联的任务是,不断关注航线被破坏的情况,并随时给出两个星球之间的关键航线数目。现在请你帮助完成。

Input

第一行有两个整数N,M。表示有N个星球(1< N < 30000),初始时已经有M条航线(1 < M < 100000)。随后有M行,每行有两个不相同的整数A、B表示在星球A与B之间存在一条航线。接下来每行有三个整数C、A、B。C为1表示询问当前星球A和星球B之间有多少条关键航线;C为0表示在星球A和星球B之间的航线被破坏,当后面再遇到C为1的情况时,表示询问航线被破坏后,关键路径的情况,且航线破坏后不可恢复; C为-1表示输入文件结束,这时该行没有A,B的值。被破坏的航线数目与询问的次数总和不超过40000。

Output

对每个C为1的询问,输出一行一个整数表示关键航线数目。 注意:我们保证无论航线如何被破坏,任意时刻任意两个星球都能够相互到达。在整个数据中,任意两个星球之间最多只可能存在一条直接的航线。

题解

  感觉这题非常好啊。

  一开始非常zz地在不是很会写,一直在思考并查集怎么做分离,不过一看到题解,就感受到了树链剖分的妙处啊。
  
  我们假设一开始只有一棵树,那么显然每条边都是关键路线。然后我们考虑加上一条边时,一定会形成一个环,这样这个环上的每条边都不是关键路径了。因为是统计两个点之间的关键路径数量,所以直接用0、1来表示是否为关键路径,最后只需统计路径和即可。到这里,可以显然地看出树链剖分了。用线段树在链上做区间覆盖和区间求和即可。
  
  不过我们怎么变成上述的模型呢?只需离线处理,先将所有的边都读入,不过怎么记录被删除的边呢?这里又可以使用hash法,将边hash成数字(通过所连接的点随便搞搞出一个独特的值),在map的帮助下表示这条边有没有被摧毁。所以全部读入完后我们还剩一些边,用并查集来建树。然后反向操作一开始记录的读入,往树上加边就是一个区间覆盖。
  
  这样,这题的思路就非常地显然了。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>

#define N 30010
#define M 100010
#define C 40010
using namespace std;

map <int,bool> hash;
int f[N];
struct node1{int x,y;}edge[M],not_tree[M];
struct node2{int to,next;}e[M*2];
int head[N],tot,t,tim,n;
int dep[N],fa[N],top[N],son[N],size[N],tid[N];
int x[C],y[C],opt[C],ans[C];

void init() {
    tot = 0;//the number of ok edge
    t = 0; tim = 0;
    memset(son,-1,sizeof(son));
    memset(head,0,sizeof(head));
}

int find(int x) { return x == f[x] ? x : f[x] = find(f[x]); }

void add_edge(int from,int to) {
    e[++tot].next = head[from]; head[from] = tot; e[tot].to = to;
}

void dfs1(int v,int pa,int d)
{
    dep[v] = d; fa[v] = pa; size[v] = 1;
    for(int i = head[v];i;i=e[i].next)
        if(e[i].to != pa) {
            dfs1(e[i].to,v,d+1);
            size[v] += size[e[i].to];
            if(son[v] == -1 || size[e[i].to] > size[son[v]])
                son[v] = e[i].to;
        }
}

void dfs2(int v,int tp)
{
    top[v] = tp; tid[v] = ++tim;
    if(son[v] == -1) return;
    dfs2(son[v],tp);
    for(int i = head[v];i;i=e[i].next) {
        if(e[i].to != son[v] && e[i].to != fa[v])
            dfs2(e[i].to,e[i].to);
    }
}

#define lson o << 1
#define rson o << 1 | 1
int sum[N << 2],setv[N << 2];

void build(int o,int l,int r)
{
    setv[o] = -1;
    if(l == r) {sum[o] = 1; if(l == 1) sum[o] = 0; return;}
    int mid = (l+r)>>1;
    build(lson,l,mid); build(rson,mid+1,r);
    sum[o] = sum[lson] + sum[rson];
}

void pushdown(int o)
{
    if(setv[o] != -1) {
        setv[lson] = setv[rson] = 0;
        sum[lson] = sum[rson] = 0;
        setv[o] = -1;
    }
}

void update(int o,int l,int r,int ll,int rr)
{
    if(ll <= l && rr >= r) {sum[o] = setv[o] = 0;return;}
    pushdown(o);
    int mid = (l+r)>>1;
    if(ll <= mid) update(lson,l,mid,ll,rr);
    if(rr > mid) update(rson,mid+1,r,ll,rr);
    sum[o] = sum[lson] + sum[rson];
}

void change(int x,int y)
{
    while(top[x] != top[y]) {
        if(dep[top[x]] < dep[top[y]]) swap(x,y);
        update(1,1,n,tid[top[x]],tid[x]);
        x = fa[top[x]];
    }
    if(dep[x] > dep[y]) swap(x,y);
    update(1,1,n,tid[x]+1,tid[y]);
}

int query(int o,int l,int r,int ll,int rr)
{
    if(ll <= l && rr >= r) return sum[o];
    int mid = (l+r)>>1,ans = 0;
    pushdown(o);
    if(ll <= mid) ans += query(lson,l,mid,ll,rr);
    if(rr > mid) ans += query(rson,mid+1,r,ll,rr);
    return ans;
}

int ask(int x,int y)
{
    int ans = 0;
    while(top[x] != top[y]) {
        if(dep[top[x]] < dep[top[y]]) swap(x,y);
        ans += query(1,1,n,tid[top[x]],tid[x]);
        x = fa[top[x]];
    }
    if(dep[x] > dep[y]) swap(x,y);
    ans += query(1,1,n,tid[x]+1,tid[y]);
    return ans;
}

int main()
{
    int m,q;
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= m;i++) {
        scanf("%d%d",&edge[i].x,&edge[i].y);
        if(edge[i].x > edge[i].y) swap(edge[i].x,edge[i].y);
    }
    q = 0;
    while(scanf("%d",&opt[++q]) && opt[q] != -1) {
        scanf("%d%d",&x[q],&y[q]);
        if(x[q] > y[q]) swap(x[q],y[q]);
        if(opt[q] == 0) hash[ (x[q]-1)*n+y[q] ] = true;
    }

    for(int i = 1;i <= n;i++) f[i] = i;
    init();

    for(int i = 1;i <= m;i++)
    {
        int now = (edge[i].x-1)*n+edge[i].y;
        if(hash[now]) continue;
        int f1 = find(edge[i].x),f2 = find(edge[i].y);
        if(f1 != f2) {
            add_edge(edge[i].x,edge[i].y); add_edge(edge[i].y,edge[i].x);
            f[f1] = f2;
        }
        else {not_tree[++t].x = edge[i].x;not_tree[t].y = edge[i].y;}
    }

    dfs1(1,0,0); dfs2(1,1);
    build(1,1,n);
    for(int i = 1;i <= t;i++) change(not_tree[i].x,not_tree[i].y);

    for(int i = q-1;i >= 1;i--)
        if(opt[i] == 0) change(x[i],y[i]); else ans[i] = ask(x[i],y[i]);
    for(int i = 1;i < q;i++)
        if(opt[i]) printf("%d\n",ans[i]);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值