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

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 4 1 2 1 1 1 1 2 2 2 2 2 2 2 1 5 3 1 2 3 1 3 4 5 3 2 3 3 1 5 2 3 3 3 0 0
 

Sample Output
  
  
1 2 2 3 3 0 2
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
 

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 WIN
typedef __int64 LL;
#define iform "%I64d"
#define oform "%I64d\n"
#define oform1 "%I64d"
#else
typedef 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 权值 + qv
int 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;
}







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值