zoukankan      html  css  js  c++  java
  • HDU 5044(2014 ACM-ICPC上海网络赛)

    题意:给定一个树形图,节点10^5,有两种操作,一种是把某两点间路径(路径必定唯一)上所有点的权值增加一个固定值。

    另一种也是相同操作,不同的是给边加权值。操作次数10^5。求操作过后,每个点和每条边的权值。

    分析:此题时间卡得非常紧,最好用输入外挂,最好不要用RMQ来求解LCA。

    此题是典型的在线LCA问题,先讲讲在线LCA要怎么做。

    在线LCA有两种方法,第一种比较常见,即将其转化成RMQ问题。

    先对树形图进行深度优先遍历,遍历过程记录路线中点的途经序列,每个非叶子节点会在序列中出现多次,从一个节点A的一个子节点回到A点再走另一个子节点的时候要再次加A加入序列。

    记录序列的同时还要记录序列中每个点在树中对应的深度。以及在序列中第一次出现的位置(其实不一定非要第一个才行),主要用于根据点标号查找其在序列中对应的下标。

    此时,LCA已经转化为RMQ,如果要求a,b的LCA,只需要找到a,b在遍历序列中分别对应的位置,并在深度序列中查找以这两点为端点的区间内的最小值即可。这个最小值在遍历序列中对应的点就是他们的LCA。

    这种方法预处理O(NlogN),查询是O(1)。

    模板如下:

    //first call init_LCA(root).
    //then call LCA(a, b) to quest the LCA of a and b.
    //the graph can be both bidirected or unidirected.
    #define MAX_NODE_NUM 0
    #define MAX_EDGE_NUM 0
    #define M 30
    
    struct Edge
    {
        int v, next, id;
        Edge()
        {}
        Edge(int v, int next, int id):v(v), next(next), id(id)
        {}
    } edge[MAX_EDGE_NUM];
    
    int head[MAX_NODE_NUM];
    int edge_cnt;
    
    void init_edge()
    {
        memset(head, -1, sizeof(head));
        edge_cnt = 0;
    }
    
    void add_edge(int u, int v, int id)
    {
        edge[edge_cnt] = Edge(v, head[u], id);
        head[u] = edge_cnt++;
    }
    
    bool vis[MAX_NODE_NUM];
    int father[MAX_NODE_NUM];
    int power[M];
    int st[MAX_NODE_NUM * 2][M];
    int ln[MAX_NODE_NUM * 2];
    int seq_cnt;
    int seq[2*MAX_NODE_NUM];
    int depth[2*MAX_NODE_NUM];
    int first_appearance[MAX_NODE_NUM];
    
    //returns the index of the first minimum value in [x, y]
    void init_RMQ(int f[], int n)
    {
        int i, j;
        for (power[0] = 1, i = 1; i < 21; i++)
        {
            power[i] = 2 * power[i - 1];
        }
        for (i = 0; i < n; i++)
        {
            st[i][0] = i;
        }
        ln[0] = -1;
        for (int i = 1; i <= n; i++)
        {
            ln[i] = ln[i >> 1] + 1;
        }
        for (j = 1; j < ln[n]; j++)
        {
            for (i = 0; i < n; i++)
            {
                if (i + power[j - 1] - 1 >= n)
                {
                    break;
                }
                //for maximum, change ">" to "<"
                //for the last, change "<" or ">" to "<=" or ">="
                if (f[st[i][j - 1]] > f[st[i + power[j - 1]][j - 1]])
                {
                    st[i][j] = st[i + power[j - 1]][j - 1];
                }
                else
                {
                    st[i][j] = st[i][j - 1];
                }
            }
        }
    }
    
    int query(int x, int y)
    {
        if(x > y)
        {
            swap(x, y);
        }
        int k = ln[y - x + 1];
        //for maximum, change ">" to "<"
        //for the last, change "<" or ">" to "<=" or ">="
        if (depth[st[x][k]] > depth[st[y - power[k] + 1][k]])
            return st[y - power[k] + 1][k];
        return st[x][k];
    }
    
    
    void dfs(int u ,int current_depth)
    {
        vis[u] = true;
        first_appearance[u] = seq_cnt;
        depth[seq_cnt] = current_depth;
        seq[seq_cnt++] = u;
        for(int i = head[u]; i != -1; i = edge[i].next)
        {
            int v = edge[i].v;
            if (vis[v])
            {
                continue;
            }
            father[v] = u;
            if (!vis[v])
            {
                dfs(v, current_depth + 1);
                depth[seq_cnt] = current_depth;
                seq[seq_cnt++] = u;
            }
        }
    }
    
    void init_LCA(int root)
    {
        memset(vis, 0, sizeof(vis));
        father[root] = -1;
        seq_cnt = 0;
        dfs(root, 0);
        init_RMQ(depth, seq_cnt);
    }
    
    //O(1)
    int LCA(int u ,int v)
    {
        int x = first_appearance[u];
        int y = first_appearance[v];
        int res = query(x, y);
        return seq[res];
    }
    View Code

    另一种方法用到了DP的思想。

    用一个数组f[i][j]表示i点在树中到根节点的序列中距离i边数为2^j的点。

    那么f[i][j] = f[ f[i][j - 1] ][j - 1]。

    具体做法是,我们进行BFS,记录每个点的父节点,即f[i][0]。和每个点的深度。

    然后根据状态转移公式填充整个数组。

    在查询时,先看a,b两点谁的深度大,利用两者深度差的二进制序列,配合f数组,找到较深的点在较浅的点那层的祖先。

    然后继续使用f数组,每次向上探测2^i的距离的点两者的祖先是否为同一个,如果不是则i++后继续叠加向上探测2^i,如果是同一个则i--后重新探测。直到找到最小的公共祖先为止。

    这种方法预处理O(NlogN),查询是O(NlogN)。但与上一种方法相比,不需要dfs,而用bfs,这样可以节省很多时间。

    模板如下:

    #define MAX_NODE_NUM 0
    #define MAX_EDGE_NUM 0
    #define MAX_Q_LEN MAX_NODE_NUM
    #define M 30
    
    struct Edge
    {
        int v, next, id;
        Edge()
        {}
        Edge(int v, int next, int id):v(v), next(next), id(id)
        {}
    } edge[MAX_EDGE_NUM];
    
    int head[MAX_NODE_NUM];
    int edge_cnt;
    
    void init_edge()
    {
        memset(head, -1, sizeof(head));
        edge_cnt = 0;
    }
    
    void add_edge(int u, int v, int id)
    {
        edge[edge_cnt] = Edge(v, head[u], id);
        head[u] = edge_cnt++;
    }
    
    bool vis[MAX_NODE_NUM];
    int father[MAX_NODE_NUM][M];
    int depth[MAX_NODE_NUM];
    
    template<typename T>
    class queue
    {
        T data[MAX_Q_LEN];
        int head, rear;
    
    public:
        queue()
        {
            head = rear = 0;
        }
    
        bool empty()
        {
            return head == rear;
        }
    
        void pop()
        {
            head++;
            if (head >= MAX_Q_LEN)
                head = 0;
        }
    
        void push(T a)
        {
            data[rear++] = a;
            if (rear >= MAX_Q_LEN)
                rear = 0;
        }
    
        T front()
        {
            return data[head];
        }
    };
    
    void bfs(int root)
    {
        queue<int> q;
        q.push(root);
        seq2_cnt = 0;
        while (!q.empty())
        {
            int u = q.front();
            q.pop();
            vis[u] = true;
            seq2[seq2_cnt++] = u;
            for (int i = head[u]; i != -1; i = edge[i].next)
            {
                int v = edge[i].v;
                if (vis[v])
                {
                    continue;
                }
                father[v][0] = u;
                depth[v] = depth[u] + 1;
                q.push(v);
            }
        }
    }
    
    //index start from 1.
    void init_LCA(int root)
    {
        fill_n(vis, node_num + 1, 0);
        memset(father, 0, sizeof(father));
        bfs(root);
        bool did;
        for (int i = 1; i < M; i++)
        {
            did = false;
            for (int j = 1; j <= node_num; j++)
            {
                int k = father[j][i - 1];
                if (k <= 0)
                {
                    continue;
                }
                father[j][i] = father[k][i - 1];
                did = true;
            }
            if (!did)
            {
                break;
            }
        }
    }
    
    //O(log(n))
    int LCA(int x, int y)
    {
        if (depth[x] > depth[y])
        {
            swap(x, y);
        }
        int diff = depth[y] - depth[x];
        for (int i = 0; i < M && diff; i++)
        {
            if (diff & 1)
            {
                y = father[y][i];
            }
            diff >>= 1;
        }
        if (x == y)
        {
            return x;
        }
        int exp = 0;
        while (x != y)
        {
            if (!exp || father[x][exp] != father[y][exp])
            {
                x = father[x][exp];
                y = father[y][exp];
                exp++;
            }else
            {
                exp--;
            }
        }
        return x;
    }
    View Code

    再说说这题是怎么做的。將种操作进行一下转化,认为每次加权操作都是分别向由两点到他们的LCA的路径加权。

    还可以进行进一步的转化设a,b为要加权的路径两端点,他们的LCA为c,根节点为root。

    那么该加权操作可转化为由a和b到root分别加权,再由c到root加两倍的负权。这样正负抵消后与原操作等价。

    这样转化之后,所有的操作都便成了到根节点的操作,那么只需要將所有的操作标记在非根节点的另一个点上,然后自底向上把操作树中的每个点,將该点的子节点中的权值操作向上传递即可。

    本题不可以使用第一种方法,会超时,可能是dfs太耗时。用第二种方法虽然查询时间稍慢,但是通过了。

    代码如下:

    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <algorithm>
    #include <cmath>
    using namespace std;
    
    #define MAX_NODE_NUM 100005
    #define MAX_EDGE_NUM MAX_NODE_NUM * 2
    #define MAX_Q_LEN MAX_NODE_NUM
    #define M 30
    #define D(x) 
    
    struct Edge
    {
        int v, next, id;
        Edge()
        {}
        Edge(int v, int next, int id):v(v), next(next), id(id)
        {}
    } edge[MAX_EDGE_NUM];
    
    int head[MAX_NODE_NUM];
    int edge_cnt;
    
    void init_edge()
    {
        memset(head, -1, sizeof(head));
        edge_cnt = 0;
    }
    
    void add_edge(int u, int v, int id)
    {
        edge[edge_cnt] = Edge(v, head[u], id);
        head[u] = edge_cnt++;
    }
    
    int node_num, opr_num;
    long long edge_opr[MAX_NODE_NUM];
    long long node_opr[MAX_NODE_NUM];
    bool vis[MAX_NODE_NUM];
    long long ans_edge[MAX_EDGE_NUM];
    int father[MAX_NODE_NUM][M];
    int depth[MAX_NODE_NUM];
    int seq2[MAX_NODE_NUM];
    int seq2_cnt;
    
    template<typename T>
    class queue
    {
        T data[MAX_Q_LEN];
        int head, rear;
    
    public:
        queue()
        {
            head = rear = 0;
        }
    
        bool empty()
        {
            return head == rear;
        }
    
        void pop()
        {
            head++;
            if (head >= MAX_Q_LEN)
                head = 0;
        }
    
        void push(T a)
        {
            data[rear++] = a;
            if (rear >= MAX_Q_LEN)
                rear = 0;
        }
    
        T front()
        {
            return data[head];
        }
    };
    
    void bfs(int root)
    {
        queue<int> q;
        q.push(root);
        seq2_cnt = 0;
        while (!q.empty())
        {
            int u = q.front();
            q.pop();
            vis[u] = true;
            seq2[seq2_cnt++] = u;
            for (int i = head[u]; i != -1; i = edge[i].next)
            {
                int v = edge[i].v;
                if (vis[v])
                {
                    continue;
                }
                father[v][0] = u;
                depth[v] = depth[u] + 1;
                q.push(v);
            }
        }
    }
    
    //index start from 1.
    void init_LCA(int root)
    {
        fill_n(vis, node_num + 1, 0);
        memset(father, 0, sizeof(father));
        bfs(root);
        bool did;
        for (int i = 1; i < M; i++)
        {
            did = false;
            for (int j = 1; j <= node_num; j++)
            {
                int k = father[j][i - 1];
                if (k <= 0)
                {
                    continue;
                }
                father[j][i] = father[k][i - 1];
                did = true;
            }
            if (!did)
            {
                break;
            }
        }
    }
    
    int LCA(int x, int y)
    {
        if (depth[x] > depth[y])
        {
            swap(x, y);
        }
        int diff = depth[y] - depth[x];
        for (int i = 0; i < M && diff; i++)
        {
            if (diff & 1)
            {
                y = father[y][i];
            }
            diff >>= 1;
        }
        if (x == y)
        {
            return x;
        }
        int exp = 0;
        while (x != y)
        {
            if (!exp || father[x][exp] != father[y][exp])
            {
                x = father[x][exp];
                y = father[y][exp];
                exp++;
            }else
            {
                exp--;
            }
        }
        return x;
    }
    
    inline int read_int()
    {
        int num = 0;
        int sign = 1;
        bool skip = false;
        int c = 0;
        while((c = getchar()) != EOF)
        {
            if(c == '-')
            {
                sign = -1;
                skip = true;
            }
            else if(c >= '0' && c <= '9')
            {
                num = num * 10 + c - '0';
                skip = true;
            }
            else if(skip)
        {
            break;
        }
        }
        return num * sign;
    }
    
    inline int ReadOP()
    {
        int c = 0;
        while((c = getchar()) != EOF && c != 'A');
        getchar(); getchar();
        return getchar();
    }
    
    void input()
    {
        scanf("%d%d", &node_num, &opr_num);
        for (int i = 0; i < node_num - 1; i++)
        {
            int a, b;
            a = read_int();
            b = read_int();
            add_edge(a, b, i);
            add_edge(b, a, i);
        }
        init_LCA(1);
        fill_n(edge_opr, node_num + 1, 0);
        fill_n(node_opr, node_num + 1, 0);
        fill_n(ans_edge, node_num + 1, 0);
        for (int i = 0; i < opr_num; i++)
        {
            int a, b, k;
            int op = ReadOP();
            a = read_int();
            b = read_int();
            k = read_int();
            int c = LCA(a, b);
            D(printf("%d
    ", c));
            if (op == '2')
            {
                edge_opr[c] -= k * 2;
                edge_opr[a] += k;
                edge_opr[b] += k;
            }else
            {
                node_opr[c] -= k;
                if (father[c][0] > 0)
                {
                    node_opr[father[c][0]] -= k;
                }
                node_opr[a] += k;
                node_opr[b] += k;
            }
        }
    }
    
    void work()
    {
        for (int i = seq2_cnt - 1; i >= 0; i--)
        {
            int u = seq2[i];
            D(printf("%d %lld
    ", u, node_opr[u]));
            for (int j = head[u]; j != -1; j = edge[j].next)
            {
                int v = edge[j].v;
                if (v == father[u][0])
                {
                    continue;
                }
                node_opr[u] += node_opr[v];
                edge_opr[u] += edge_opr[v];
                ans_edge[edge[j].id] = edge_opr[v];
            }
            D(printf("%d %lld
    ", u, node_opr[u]));
        }
    }
    
    void output()
    {
        bool first = true;
        for (int i = 1; i <= node_num; i++)
        {
            if (first)
            {
                first = false;
            }else
            {
                putchar(' ');
            }
            printf("%lld", node_opr[i]);
        }
        puts("");
        first = true;
        for (int i = 0; i < node_num - 1; i++)
        {
            if (first)
            {
                first = false;
            }else
            {
                putchar(' ');
            }
            printf("%lld", ans_edge[i]);
        }
        puts("");
    }
    
    int main()
    {
        int t;
        scanf("%d", &t);
        for (int i = 0; i < t; i++)
        {
            printf("Case #%d:
    ", i + 1);
            init_edge();
            seq2_cnt = 0;
            input();
            work();
            output();
        }
        return 0;
    }
    View Code
  • 相关阅读:
    【译】NodeJS and Good Practices
    【译】单一责任原则
    CSS 属性 z-index
    Node 连接 MySql
    CentOS 7 后台克隆远程库
    H5log打印
    利用Promise实现Promise.all
    Set、Map、WeakSet、WeakMap 理解与使用
    vue如何禁止弹窗后面的滚动条滚动?
    vue面试题总结
  • 原文地址:https://www.cnblogs.com/rainydays/p/4009519.html
Copyright © 2011-2022 走看看