5029 Relief grain 树链剖分+离线标记法

Relief grain

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 100000/100000 K (Java/Others)
Total Submission(s): 1773    Accepted Submission(s): 438



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}. 转自 http://blog.csdn.net/u013368721/article/details/39449273

题目分析:这题的方法太美妙了!

首先看到这是一颗树,那么很容易想到树链剖分。然后想到可以将查询排个序,然后每一种查询执行完以后更新每个点的最优值。但是这样进行的复杂度太高!尤其是当z给的没有一样的时候尤其如此。

那么我们是否可以找到更加高效的算法?

答案是肯定的!

先简化一下问题,如果这些操作是在线段上进行的,我们怎么求解?

我们很容易可以想到标记法:区间【L,R】染上颜色X,则位置L标记为X,表示从L开始染色X,位置R+1标记为-X,表示从R+1开始结束染色。用邻接表保存当前位置上的标记,然后从左往右,每到一个点上就把所有的标记执行了,为此我们建立一棵权值线段树,+X,我们就在位置X上+1,-X,我们就在位置X上-1,然后用maxv标记记录区间最大值。执行完这些操作以后就是查询了,顺着maxv == 最大值走,尽量往左走就行了。

然后我们回到本题,完全就是一个套路啊!

只不过把连续的区间分成logN个不是连续的区间而已。还是按照上面的操作就可以了,不过要注意扫描时的点是线段树上的点,而不是原来的点,还要映射回去。

#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <ctime>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define inf -0x3f3f3f3f
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define mem0(a) memset(a,0,sizeof(a))
#define mem1(a) memset(a,-1,sizeof(a))
#define mem(a, b) memset(a, b, sizeof(a))
typedef long long ll;
const int maxn=101010;
int p,v,tot;
vector<int>G[maxn];
int siz[maxn],son[maxn],fa[maxn],deep[maxn],top[maxn],id[maxn];
int fp[maxn],maxv[maxn*4],num[maxn*4],ans[maxn];

void scanf ( int& x , char c = 0 ) {  
    while ((c=getchar())<'0'||c>'9');  
    x = c - '0' ;  
    while ( ( c = getchar () ) >= '0' && c <= '9' ) x = x * 10 + c - '0' ;  
}  

void dfs1(int u,int dep){
    deep[u]=dep;
    siz[u]=1,son[u]=0;
    for(int i=0;i<G[u].size();i++){
        int v=G[u][i];
        if(v==fa[u])
            continue;
        fa[v]=u;
        dfs1(v,dep+1);
        if(siz[v]>siz[son[u]])
            son[u]=v;
        siz[u]+=siz[v];
    }
}

void dfs2(int u,int tp){
    top[u]=tp;
    id[u]=++tot,fp[id[u]]=u;
    if(son[u]!=0)
        dfs2(son[u],tp);
    for(int i=0;i<G[u].size();i++){
        int v=G[u][i];
        if(v==fa[u]||v==son[u])
            continue;
        dfs2(v,v);
    }
}

void pushup(int rt){
    if(maxv[rt<<1]>=maxv[rt<<1|1]){
        maxv[rt]=maxv[rt<<1];
        num[rt]=num[rt<<1];
    }
    else{
        maxv[rt]=maxv[rt<<1|1];
        num[rt]=num[rt<<1|1];
    }
}

void build(int l,int r,int rt){
    if(l==r){
        maxv[rt]=0;
        num[rt]=l;
        return ;
    }
    int m=(l+r)>>1;
    build(lson);
    build(rson);
    pushup(rt);
}

void update(int l,int r,int rt){
    if(l==p&&r==p){
        maxv[rt]+=v;
        return ;
    }
    int m=(l+r)>>1;
    if(p<=m)
        update(lson);
    else
        update(rson);
    pushup(rt);
}

vector<int>avec[maxn];
vector<int>dvec[maxn];

void Update(int u,int v,int z){
    int f1=top[u],f2=top[v];
    while(f1!=f2){
        if(deep[f1]<deep[f2]){
            swap(f1,f2);
            swap(u,v);
        }
        avec[id[f1]].push_back(z);
        dvec[id[u]+1].push_back(z);
        u=fa[f1],f1=top[u];
    }
    if(deep[u]>deep[v]){
        swap(u,v);
    }
    avec[id[u]].push_back(z);
    dvec[id[v]+1].push_back(z);
}

void init(int n){
    for(int i=1;i<=n;i++)
        G[i].clear();
    mem0(maxv);
    mem0(son);
    tot=0;
}

int main(){
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF){
        if(n==0&&m==0)
            break;
        init(n);
        int u,z;
        for(int i=1;i<n;i++){
            scanf(u);
            scanf(v);
            G[u].push_back(v);
            G[v].push_back(u);
        }
        fa[1]=0,siz[0]=0;
        dfs1(1,0);
        dfs2(1,0);
        for(int i=1;i<=100007;i++){
            avec[i].clear();
            dvec[i].clear();
        }
        build(1,100010,1);
        for(int i=0;i<m;i++){
            scanf("%d%d%d",&u,&v,&z);
            Update(u,v,z);
        }
        for(int i=1;i<=tot;i++){
            for(int j=0;j<avec[i].size();j++){
                p=avec[i][j],v=1;
                update(1,100010,1);
            }
            for(int j=0;j<dvec[i].size();j++){
                p=dvec[i][j],v=-1;
                update(1,100010,1);
            }
            u=fp[i];
            if(maxv[1]==0)
                ans[u]=0;
            else
                ans[u]=num[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、付费专栏及课程。

余额充值