HDU 5029 Relief grain 树链剖分 离线 线段树

题意:给出一个树。我们有以下操作:在节点u,v的路径上,对所有节点加入一个给定种类的货物。最后,对每个节点输出该点数目最多的货物的数量。

思路:看到树上的路径,是不是该想到树链剖分呢?但是因为货物的种类太多了,没法状态压缩。 但是,因为相当于是离线查询,我们是可以离线搞搞的。

           首先利用树链剖分,利用差分的思想,打上标记。这样,我们就可以从前往后根据重标号,从前往后递推就可以了。

           然后,我们需要一个东西来维护两个关键字,即:先比较货物的数量,在货物数量相同的时候,比较货物的编号。用set就可以了。

代码如下:

#pragma comment(linker,"/STACK:102400000,102400000")

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <set>

using namespace std;

const int MAX = 100100;

struct event{
    int po,va,ty;
    event(int p,int v,int t):po(p),va(v),ty(t){}
    bool operator < (const event & rhs) const{
        if(po != rhs.po) return po < rhs.po;
        else return ty > rhs.ty;
    }
};

struct point{
    int x, y;
    point(int xx, int yy):x(xx),y(yy){}
    bool operator < (const point & rhs) const{
        if(x != rhs.x) return x > rhs.x;
        else return y < rhs.y;
    }
};

vector<event> Event;
set<point> S;
int cnt[MAX];
int ans[MAX];
int N,M;
int x,y,z;
int head[MAX],nxt[MAX<<2],to[MAX<<2],tot;
int fa[MAX];//father of node
int top[MAX];//top node of every heavy chain
int deep[MAX];// deepth of every node
int num[MAX];// the number of node's children
int p[MAX];//new index of each node
int fp[MAX];//the node number of new index
int son[MAX];//heavy son of each node
int pos;// count of new index

void init()
{
    Event.clear();
    S.clear();
    tot = pos = 0;
    memset(head,-1,sizeof(head));
    memset(son,-1,sizeof(son));
    memset(cnt,0,sizeof(cnt));
}

void addedge(int u, int v)
{
    to[tot] = v; nxt[tot] = head[u];
    head[u] = tot++;
    to[tot] = u; nxt[tot] = head[v];
    head[v] = tot++;
}

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 = nxt[i])
	{
		int v = to[i];
		if(v != pre)
		{
			dfs1(v,u,d+1);
			num[u] += num[v];
			if(son[u] == -1 || num[v] > num[son[u]])
				son[u] = v;
		}
	}
}

void dfs2(int u, int sp)
{
    top[u] = sp; p[u] = pos++; fp[p[u]] = u;
    if(son[u] != -1){
        dfs2(son[u],sp);
        for(int i = head[u]; i != -1; i = nxt[i]){
            int v = to[i];
            if(v != son[u] && v != fa[u])
                dfs2(v,v);
        }
    }
}

//update of node of value
//the index of node is the
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);
        }
        Event.push_back(event(p[f1],z,1));
        Event.push_back(event(p[u]+1,z,-1));
        u = fa[f1]; f1 = top[u];
    }
    if(deep[u] > deep[v]) swap(u,v);
    Event.push_back(event(p[u],z,1));
    Event.push_back(event(p[v]+1,z,-1));
}

void erase(int ty)
{
    set<point>::iterator it = S.find(point(cnt[ty],ty));
    if(it != S.end()) S.erase(it);
    if(cnt[ty]) cnt[ty]--;
    if(cnt[ty]) S.insert(point(cnt[ty],ty));
}

void insert(int ty)
{
    set<point>::iterator it = S.find(point(cnt[ty],ty));
    if(it != S.end()) S.erase(it);
    cnt[ty]++;
    if(cnt[ty]) S.insert(point(cnt[ty],ty));
}

int main(void)
{
    //freopen("input.txt","r",stdin);
    while(scanf("%d %d", &N, &M), N||M){
        init();
        for(int i = 0; i < N - 1; ++i){
            scanf("%d %d", &x,&y);
            addedge(x,y);
        }
        dfs1(1,0,0);
        dfs2(1,1);
        for(int i = 0; i < M; ++i){
            scanf("%d %d %d", &x,&y,&z);
            update(x,y,z);
        }
        sort(Event.begin(),Event.end());
        for(int i = 0, sz = Event.size(),j = 0; i < pos; ++i){
            while(j < sz && Event[j].po <= i){
                if(Event[j].ty == -1) erase(Event[j].va);
                else insert(Event[j].va);
                ++j;
            }
            if(S.empty()) ans[i] = 0;
            else ans[i] = S.begin()->y;
        }
        for(int i = 1; i <= N; ++i)
            printf("%d\n",ans[p[i]]);
    }
    return 0;
}

第二种思路:

我们可以不偷懒,在树链剖分的前提下,用线段树来求解。

开始的步骤一样,我们首先对剖分后的区间打标记。

然后,我们建立线段树。在这道题中的线段树是保存的:在当前树上的节点下,各个种类货物的数量。对于每个线段树的节点,保存的是,对应货物编号区间的数量的最大值。 

然后,对于原来树上的节点,我们从前向后递推,把对应的标记加入线段树中。查询最大的数量和对应的编号,我们从上往下尽可能的,向下向左走(最小的编号),同时保证该区间的最大值为整个区间上的最大值。

代码可见:http://blog.csdn.net/u013368721/article/details/39449273


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值