HDU 5044(2014 ACM-ICPC上海网络赛)

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

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

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

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

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

先对树形图进行深度优先遍历,遍历过程记录路线中点的途经序列,每个非叶子节点会在序列中出现多次,从一个节点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];
}

另一种方法用到了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;
}

再说说这题是怎么做的。將种操作进行一下转化,认为每次加权操作都是分别向由两点到他们的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\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;
}

时间: 2024-12-26 08:03:34

HDU 5044(2014 ACM-ICPC上海网络赛)的相关文章

hdu 5008(2014 ACM/ICPC Asia Regional Xi&#39;an Online ) Boring String Problem(后缀数组&amp;二分)

Boring String Problem Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total Submission(s): 219    Accepted Submission(s): 45 Problem Description In this problem, you are given a string s and q queries. For each que

HDU 5000 2014 ACM/ICPC Asia Regional Anshan Online DP

Clone Time Limit : 2000/1000ms (Java/Other)   Memory Limit : 65536/65536K (Java/Other) Total Submission(s) : 8   Accepted Submission(s) : 5 Font: Times New Roman | Verdana | Georgia Font Size: ← → Problem Description After eating food from Chernobyl,

2019 ICPC上海网络赛 G. Substring 哈希+尺取法+unordered_map

题目链接:https://nanti.jisuanke.com/t/41415 赛后补题. 参考博客:https://blog.csdn.net/bjfu170203101/article/details/100889468 题意:给出一个主串(假设长度为m),再给出n个模式串,对于每一个模式串,如果在主串中有一个子串,它的第一个字符和最后一个字符分别与这个模式串的开头字符和结尾字符相同,并且每一种字符出现的次数相同,那么就把这个模式串和这个子串看成是相同的,认为模式串在主串中出现过一次,比如模

HDU 5014 Number Sequence(2014 ACM/ICPC Asia Regional Xi&#39;an Online) 题解

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5014 Number Sequence Problem Description There is a special number sequence which has n+1 integers. For each number in sequence, we have two rules: ● ai ∈ [0,n] ● ai ≠ aj( i ≠ j ) For sequence a and sequ

2014 ACM/ICPC Asia Regional Xi&#39;an Online(HDU 5007 ~ HDU 5017)

题目链接 A题:(字符串查找,水题) 题意 :输入字符串,如果字符串中包含“ Apple”, “iPhone”, “iPod”, “iPad” 就输出 “MAI MAI MAI!”,如果出现 “Sony” 就输出“SONY DAFA IS GOOD!” ,大小写敏感. 思路 : 字符串查找,水题. 1 #include <string.h> 2 #include <stdio.h> 3 #include <iostream> 4 5 using namespace st

HDU 5014 Number Sequence 贪心 2014 ACM/ICPC Asia Regional Xi&#39;an Online

尽可能凑2^x-1 #include <cstdio> #include <cstring> const int N = 100005; int a[N], p[N]; int init(int x) { int cnt = 0; while(x > 1) { x /= 2; cnt ++; } return cnt + 1; } int main() { int n; while(~scanf("%d", &n)){ for(int i = 0;

HDU 5010 Get the Nut(2014 ACM/ICPC Asia Regional Xi&#39;an Online)

思路:广搜, 因为空格加上动物最多只有32个那么对这32个进行编号,就能可以用一个数字来表示状态了,因为只有 ‘P’   'S' 'M' '.' 那么就可以用4进制刚好可以用64位表示. 接下去每次就是模拟了. 注意:  ‘S’ 不是只有一个. 一个东西如果不是'P'在动的话要先判断周围有没有‘P’,有的话要先吃掉      'P'在动的时候如果一个位置周围有多个东西,都要吃掉. #include<iostream> #include<cstdio> #include<alg

hdu 5016 点分治(2014 ACM/ICPC Asia Regional Xi&#39;an Online)

Mart Master II Time Limit: 12000/6000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 675    Accepted Submission(s): 237 Problem Description Trader Dogy lives in city S, which consists of n districts. There are n - 1

2014 ACM/ICPC Asia Regional Xi&#39;an Online 233 Matrix,hdu 5015

比赛的时候若是这题过了就进前50 刚开始的时候大家的思路都以为是找规律的题目,于是再推公式,此外还发现类似于杨辉三角.于是又去套杨辉三角的通项去求. 于是TLE了无数次.(每次取范围的最大值也要3s多). 对于明显的矩阵样子,其实可以转化为矩阵的运算,每一行的转移.就是对一个转移矩阵的幂运算.然后再用快速矩阵幂即可. A: 10 0 0 1 10 1 0 1 10 1 1 1 0  0  0 1 B: 23 0 0 3 C=A^M  *B,ans=C[N] 教训:对于时间限制,即便是最大数据也要

HDU 4998 Rotate(计算几何)2014年鞍山赛区网络赛

Rotate                                                                           Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Special Judge Problem Description Noting is more interesting than rotation! Your litt