HDU 5029 - Relief grain (树链剖分 很巧妙的离线标记法)

来源:互联网 发布:时序数据挖掘 编辑:程序博客网 时间:2024/06/06 00:58

Relief grain

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 100000/100000 K (Java/Others)

Total Submission(s): 745    Accepted Submission(s): 182



Problem Description
The soil is cracking up because of the drought and the rabbit kingdom is facing a serious famine. The RRC(Rabbit Red Cross) organizes the distribution of relief grain in the disaster area.

We can regard the kingdom as a tree with n nodes and each node stands for a village. The distribution of the relief grain is divided into m phases. For each phases, the RRC will choose a path of the tree and distribute some relief grain of a certain type for every village located in the path.

There are many types of grains. The RRC wants to figure out which type of grain is distributed the most times in every village.
 

Input
The input consists of at most 25 test cases.

For each test case, the first line contains two integer n and m indicating the number of villages and the number of phases.

The following n-1 lines describe the tree. Each of the lines contains two integer x and y indicating that there is an edge between the x-th village and the y-th village.
  
The following m lines describe the phases. Each line contains three integer x, y and z indicating that there is a distribution in the path from x-th village to y-th village with grain of type z. (1 <= n <= 100000, 0 <= m <= 100000, 1 <= x <= n, 1 <= y <= n, 1 <= z <= 100000)

The input ends by n = 0 and m = 0.
 

Output
For each test case, output n integers. The i-th integer denotes the type that is distributed the most times in the i-th village. If there are multiple types which have the same times of distribution, output the minimal one. If there is no relief grain in a village, just output 0.
 

Sample Input
2 41 21 1 11 2 22 2 22 2 15 31 23 13 45 32 3 31 5 23 3 30 0
 

Sample Output
1223302
Hint
For the first test case, the relief grain in the 1st village is {1, 2}, and the relief grain in the 2nd village is {1, 2, 2}.
 

Source
2014 ACM/ICPC Asia Regional Guangzhou Online
 

Recommend
hujie   |   We have carefully selected several similar problems for you:  5053 5052 5051 5050 5049 
 

Statistic | Submit | Discuss | Note



题意:

一颗数上,初始每个点都是空的

然后执行 x y z,表示给x->y的所有点一个z物品

问最后执行完所有操作,输出每个节点数量最多的物品编号,如果多个物品一样多,输出物品编号最小的编号



树链剖分都知道,关键是维护

都能想到的按z排序然后按z相同的处理,相同的处理完了更新答案,并清空线段树。

但是复杂度太高了

看了别人的做法,感觉非常好

离线标记法都知道:

在一段区间上,执行多个[L, R] + v后求每个点的权值。L位置+v,R+1位置-v。然后扫一遍就行了。

void SegAdd(int L, int R, int v) {    Seg[L] += v;    Seg[R+1] -= v;}void Scan(int L, int R) {    int cur = Seg[L];    for(int i=L+1; i<=R; i++) {        int t = Seg[i];        Seg[i] += cur;        cur += t;    }}


然后看这个题:

先假设是在区间执行x y z操作,表示[x, y]的z物品数量+1

类比上面的离线标记法:

看代码,cur表示当前的增加值,然后给当前点+cur,并维护cur。

这是一个物品的情况

如果有两个物品 那么可以通过这有来求每个点的两种物品数量

seg1[maxn], seg2[maxn]来表示两种物品的数量,ans[maxn]表示数量最大的物品编号

int cur1 = Seg1[L],cur2 = Seg2[L];

for(int i = L; i <= R; i++) {

cur1 ..

cur2...

}

也是同样的扫描方法 就不写了

现在考虑多个物品的情况,不可能用maxn个Seg来求,时间空间都不允许

对于空间:

对于所有的(Segz表示用来维护z物品的数组),可以合并起来,借助 vector<int> SegAdd[maxn], SegSub[maxn];

这样Segz[L]+1 Segz[R+1]-1就对应与SegAdd[L].push_back(z) SegSub[R+1].push_back(z);

这有扫描的时候,在当前点i时,取出SegAdd[i] SegSub[i]里面的所有z:z1 z2 z1 z3 ....

则对应:如果是SegAdd,Cnt[zi]++, 如果是SegSub Cnt[zi]--。这样每个点的所有物品数量都在Cnt里面

这样空间问题解决了

对于时间:

现在要求出Cnt里面的最大值,线段树就可以解决了。

现在考虑在树上,树链剖分,把所有点hash到线段的点上去,就变成了上面的问题了

至此 问题已解决



#pragma comment(linker, "/STACK:1024000000,1024000000")#include <cstdio>#include <iostream>#include <vector>#include <algorithm>#include <cstring>#include <string>#include <map>#include <cmath>#include <queue>#include <set>using namespace std;//#define WIN#ifdef WINtypedef __int64 LL;#define iform "%I64d"#define oform "%I64d\n"#define oform1 "%I64d"#elsetypedef long long LL;#define iform "%lld"#define oform "%lld\n"#define oform1 "%lld"#endif#define S64I(a) scanf(iform, &(a))#define P64I(a) printf(oform, (a))#define P64I1(a) printf(oform1, (a))#define REP(i, n) for(int (i)=0; (i)<n; (i)++)#define REP1(i, n) for(int (i)=1; (i)<=(n); (i)++)#define FOR(i, s, t) for(int (i)=(s); (i)<=(t); (i)++)const int INF = 0x3f3f3f3f;const double eps = 1e-9;const double PI = (4.0*atan(1.0));const int maxn = 100000 + 20;const int maxo = maxn * 4;struct Edge {    int to,next;} edge[maxn*2];int head[maxn], tot;int top[maxn]; // top[v]表示v所在的重链的顶端节点int fa[maxn];  // 父亲节点int deep[maxn]; // 深度int num[maxn]; // num[v]表示以v为根的子树的节点数int p[maxn]; // p[v]表示v与其父亲节点的连边在线段树中的位置int fp[maxn]; // 和p数组相反int son[maxn]; // 重儿子int pos; // 对应区间计数器// 离线标记法vector<int> addv[maxn];vector<int> subv[maxn];void init() {    tot = 0;    memset(head, -1, sizeof(head));    pos = 1;    memset(son, -1, sizeof(son));}void addEdge(int u, int v) {    edge[tot].to = v; edge[tot].next = head[u]; head[u] = tot++;}// 第一遍dfs求出fa, deep, num, son// u当前节点, pre为父节点, d为深度void dfs1(int u, int pre, int d) {    deep[u] = d;    fa[u] = pre;    num[u] = 1;    for(int i = head[u]; i != -1; i = edge[i].next) {        int v = edge[i].to;        if(v != pre) {            dfs1(v, u, d+1);            num[u] += num[v];            if(son[u] == -1 || num[v] > num[son[u]])                son[u] = v;        }    }}// 第二遍dfs求出top和p// sp为当前点所在重链的顶节点void dfs2(int u, int sp) {    top[u] = sp;    p[u] = pos++;    fp[p[u]] = u;    if(son[u] == -1) return;    dfs2(son[u], sp);    for(int i = head[u]; i != -1; i = edge[i].next) {        int v = edge[i].to;        if(v != son[u] && v != fa[u])            dfs2(v, v);    }}void distribute(int u, int v, int z) {    int tu = u, tv = v, tz = z;    int nn = 0;    while(top[u] != top[v]) {        if(deep[top[u]] < deep[top[v]]) swap(u, v);        addv[p[top[u]]].push_back(z);        subv[p[u]+1].push_back(z);        u = fa[top[u]];    }    if(deep[u] > deep[v]) swap(u, v);    addv[p[u]].push_back(z);    subv[p[v]+1].push_back(z);}// 线段树维护最大值int maxv[maxo], maxvId[maxo];void pushUp(int o) {    if(maxv[o<<1] >= maxv[o<<1|1]) {        maxv[o] = maxv[o<<1];        maxvId[o] = maxvId[o<<1];    } else {        maxv[o] = maxv[o<<1|1];        maxvId[o] = maxvId[o<<1|1];    }}// qx 权值 + qvint qx, qv;void update(int o, int L, int R) {    if(L == qx && qx == R) {        maxv[o] += qv;        maxvId[o] = qx;        return ;    }    int M = L + (R-L) / 2;    if(qx <= M) update(o<<1, L, M);    else update(o<<1|1, M+1, R);    pushUp(o);}int ans[maxn];int main() {    int n, m;    while(scanf("%d%d", &n, &m) != EOF && n+m) {        init();        for(int i=0; i<n-1; i++) {            int u, v;            scanf("%d%d", &u, &v);            addEdge(u, v);            addEdge(v, u);        }        dfs1(1, 1, 1);        dfs2(1, 1);        for(int i=1; i<=pos; i++) addv[i].clear(), subv[i].clear();        int mz = 0;        for(int i=0; i<m; i++) {            int u, v, z;            scanf("%d%d%d", &u, &v, &z);            mz = max(mz, z);            distribute(u, v, z);        }        memset(maxv, 0, sizeof(maxv));        for(int i=1; i<pos; i++) {            for(int j=0; j<addv[i].size(); j++) {                qx = addv[i][j], qv = 1;                update(1, 1, mz);            }            for(int j=0; j<subv[i].size(); j++) {                qx = subv[i][j], qv = -1;                update(1, 1, mz);            }            if(maxv[1] == 0) ans[fp[i]] = 0;            else ans[fp[i]] = maxvId[1];        }        for(int i=1; i<=n; i++) printf("%d\n", ans[i]);    }    return 0;}







0 0