• ACM学习历程—SNNUOJ 1110 传输网络((并查集 && 离线) || (线段树 && 时间戳))(2015陕西省大学生程序设计竞赛D题)


    Description

    Byteland国家的网络单向传输系统可以被看成是以首都 Bytetown为中心的有向树,一开始只有Bytetown建有基站,所有其他城市的信号都是从Bytetown传输过来的。现在他们开始在其他城市陆 续建立了新的基站,命令“C x“代表在城市x建立了一个新的基站,不会在同一个城市建立多个基站;城市编号为1到n,其中城市1就是首都Bytetown。在建立基站的过程中他们还 会询问某个城市的网络信号是从哪个城市传输过来的,命令”Q x“代表查询城市x的来源城市。

    Input

    输 入有多组测试数据。每组数据的第一行包含两个正整数n和m(1 <= n,m <= 100,000),分别代表城市数和命令数。接下来n-1行,每行两个正整数u和v,代表一条从城市u到城市v的网络传输通道。之后的m行,每行一个命 令”C x“或”Q x”。
    所有输入的n和m的总和分别都不超过500,000,两组输入数据之间用一个空行隔开。

    Output

    对于每个查询命令,输出一个整数y,表示来源城市。每两组数据之间用一个空格隔开。

    Sample Input

    3 4
    1 2
    2 3
    Q 3
    C 2
    Q 2
    Q 3

    Sample Output

    1
    2
    2

    题目大意是给了一个树,一开始所有结点的来源都是编号为1的那个结点。然后可以通过C操作来将某个城市设为来源城市,通过Q操作来查询最近的祖先来源城市。
    当时省赛时的第一反应是用带时间戳的线段树去解决,但是没有看清查询的是最近的祖先来源城市,当成了纯粹的染色,果断写跪了。
    于是这道题理论上可以有两种解法。不过本地测时,用递归去得到时间戳,深度很深会爆。

    解法一:(带时间戳的线段树)
    这个和线段树的苹果那题很像,首先通过dfs(当然理论上可以不通过递归实现),得到每个结点的左值和右值;
    其中右值代表新编号,即是在dfs中后序遍历的标号。
    左值代表子树中右值的最小值,即子树中的最小编号。
    通过手画一张图基本上可以知道递归时的操作。

    然后就是如何诠释C和Q操作了。
    C操作原本是将原编号设为来源城市,即将子树中左值到右值区间内的所有结点染为当前城市编号,由于是染最近祖先来源城市,而且在树中时间戳从上往下是变小的。所以这一步应该是染结点的右值,而且进行懒人操作的pushDown时应该更新右值小的那一个。
    Q操作是查询某个点,自然是查询这个点的右值到这个点的右值这个单点区间。得到的是时间戳的右值,再通过Hash回去,得到原编号即可。

    解法二:(并查集离线查询)
    由于查询操作和点修改操作是混合的,所以查询的时候不能带路径压缩,否则树的结构会被破坏。
    但是如果所有的C操作都完成后,对剩余的Q操作便可以进行路径压缩。
    于是考虑能不能从最后一个C操作还原到倒数第二个C操作,这样的话,就可以对这两个C直接的Q进行路径压缩。
    由于C操作仅是将结点指向自己,所以还原C操作就是将结点指向原来的父节点。这样就只需要记录每个点的父节点。
    综上可以对查询进行离线:先正着来一遍,只执行C操作,然后倒着查询,遇到C操作还原,遇到Q操作进行路径压缩,并将查询结果存入数组。

    代码:(带时间戳的线段树)

    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <cmath>
    #include <set>
    #include <map>
    #include <queue>
    #include <string>
    #include <algorithm>
    #define LL long long
    
    using namespace std;
    
    const int maxN = 100005;
    
    //链式前向星
    struct Edge
    {
        int to, next;
    }edge[maxN];
    
    int head[maxN], cnt;
    
    void addEdge(int u, int v)
    {
        edge[cnt].to = v;
        edge[cnt].next = head[u];
        head[u] = cnt;
        cnt++;
    }
    
    void initEdge()
    {
        memset(head, -1, sizeof(head));
        cnt = 0;
    }
    
    int n, m;
    int idL[maxN], idR[maxN];
    int Hash[maxN];
    
    void dfs(int now, int &num)
    {
        idL[now] = num;
        for (int i = head[now]; i != -1; i = edge[i].next)
            dfs(edge[i].to, num);
        idR[now] = num;
        Hash[num] = now;
        num++;
    }
    
    //线段树
    //区间染色变形,染最值
    struct node
    {
        int lt, rt;
        int val;
        int turn;
    }tree[4*maxN];
    
    //向下更新
    void pushDown(int id)
    {
        if (tree[id].turn != 0)
        {
            tree[id<<1].turn = min(tree[id].turn, tree[id<<1].val);
            tree[id<<1].val = tree[id<<1].turn;
            tree[id<<1|1].turn = min(tree[id].turn, tree[id<<1|1].val);
            tree[id<<1|1].val = tree[id<<1|1].turn;
            tree[id].turn = 0;
        }
    }
    
    //建立线段树
    void build(int lt, int rt, int id)
    {
        tree[id].lt = lt;
        tree[id].rt = rt;
        tree[id].val = idR[1];//每段的初值,根据题目要求
        tree[id].turn = 0;
        if (lt == rt)
            return;
        int mid = (lt + rt) >> 1;
        build(lt, mid, id<<1);
        build(mid + 1, rt, id<<1|1);
        //pushUp(id);
    }
    
    //修改区间值
    void change(int lt, int rt, int id, int v)
    {
        if (lt <= tree[id].lt && rt >= tree[id].rt)
        {
            tree[id].val = tree[id].turn = min(tree[id].val, v);
            return;
        }
        pushDown(id);
        int mid = (tree[id].lt + tree[id].rt) >> 1;
        if (lt <= mid)
            change(lt, rt, id<<1, v);
        if (rt > mid)
            change(lt, rt, id<<1|1, v);
        //pushUp(id);
    }
    
    //查询单点的值
    int query(int lt, int rt, int id)
    {
        if (lt <= tree[id].lt && rt >= tree[id].rt)
            return tree[id].val;
        pushDown(id);
        int mid = (tree[id].lt + tree[id].rt) >> 1;
        if (rt <= mid)
            return query(lt, rt, id<<1);
        if (lt > mid)
            return query(lt, rt, id<<1|1);
    }
    
    void input()
    {
        initEdge();
        int u, v;
        for (int i = 1; i < n; ++i)
        {
            scanf("%d%d", &u, &v);
            addEdge(u, v);
        }
        int num = 1;
        dfs(1, num);
        build(1, n, 1);
    }
    
    void work()
    {
        char str[5];
        int v;
        for (int i = 0; i < m; ++i)
        {
            scanf("%s%d", str, &v);
            if (str[0] == 'C')
                change(idL[v], idR[v], 1, idR[v]);
            else
                printf("%d
    ", Hash[query(idR[v], idR[v], 1)]);
        }
    }
    
    int main()
    {
        //freopen("test.in", "r", stdin);
        //freopen("test.out", "w", stdout);
        while (scanf("%d%d", &n, &m) != EOF)
        {
            input();
            work();
        }
        return 0;
    }
    View Code

    代码:(并查集离线查询)

    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <cmath>
    #include <set>
    #include <map>
    #include <queue>
    #include <string>
    #include <algorithm>
    #define LL long long
    
    using namespace std;
    
    const int maxN = 100005;
    
    int n, m;
    int ufs[maxN];
    int fa[maxN];
    char op[maxN][3];
    int query[maxN];
    int ans[maxN], top;
    
    int findRoot(int x)
    {
        int pre, now, rx;
        rx = x;
        while(ufs[rx] != 0)
            rx = ufs[rx];
        pre = x;
        while(pre != rx)
        {
            now = ufs[pre];
            ufs[pre] = rx;
            pre = now;
        }
        return rx;
    }
    
    void input()
    {
        memset(ufs, 0, sizeof(ufs));
        top = 0;
        int u, v;
        for (int i = 1; i < n; ++i)
        {
            scanf("%d%d", &u, &v);
            ufs[v] = u;
            fa[v] = u;
        }
        for (int i = 0; i < m; ++i)
        {
            scanf("%s%d", op[i], &query[i]);
            if (op[i][0] == 'C')
            {
                ufs[query[i]] = 0;
            }
        }
    }
    
    void work()
    {
        for (int i = m-1; i >= 0; --i)
        {
            if (op[i][0] == 'C')
            {
                ufs[query[i]] = fa[query[i]];
            }
            else
            {
                ans[top++] = findRoot(query[i]);
            }
        }
        while (top)
        {
            top--;
            printf("%d
    ", ans[top]);
        }
    }
    
    int main()
    {
        //freopen("test.in", "r", stdin);
        //freopen("test.out", "w", stdout);
        while (scanf("%d%d", &n, &m) != EOF)
        {
            input();
            work();
        }
        return 0;
    }
    View Code
  • 相关阅读:
    题目1007:奥运排序问题(自定义排序问题)
    题目1005:Graduate Admission(录取算法)
    九度OJ小结2
    题目1049:字符串去特定字符(简单字符判断)
    题目1111:单词替换(字符串查找)
    题目1168:字符串的查找删除(字符串操作)
    题目1455:珍惜现在,感恩生活(多重背包问题)
    题目1454:Piggy-Bank(完全背包问题)
    题目1453:Greedy Tino(dp题目)
    题目1452:搬寝室(dp题目)
  • 原文地址:https://www.cnblogs.com/andyqsmart/p/4680474.html
Copyright © 2020-2023  润新知