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)。

模板如下:

HDU 5044(2014 ACM-ICPC上海网络赛)
//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,这样可以节省很多时间。

模板如下:

HDU 5044(2014 ACM-ICPC上海网络赛)
#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太耗时。用第二种方法虽然查询时间稍慢,但是通过了。

代码如下:

HDU 5044(2014 ACM-ICPC上海网络赛)
#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\n", 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\n", 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\n", 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:\n", i + 1);

        init_edge();

        seq2_cnt = 0;

        input();

        work();

        output();

    }

    return 0;

}
View Code

 

你可能感兴趣的:(ICPC)