HDU 5044 Tree 树链剖分

题意:给出一个树。有两种操作,1:对节点到节点v上的路径的节点的值加K。2:对节点u到节点v上的路径的每条边的值加K。最后,把每条边和点的权值输出。

思路:看到路径就该想到树链剖分了吧。。。因为是离线的,我们可以直接打标记,然后离线算就可以了。

思考:第一次写树链剖分,思考了很长时间的点剖分和链剖分的区别。其实。两个区别是很小的。第二次dfs的时候,我们是对每个节点重新标号。所以,我们直接得到的是点的剖分。而链的剖分是由每条边的深度更大的点的标号来代替的。

注意:1.因为分别要对边和点进行操作,我们要分别统计。

           2.因为点比较多,dfs会爆栈,需要手动扩栈或者用bfs.

           3.最坑的就是在:整个图会有一个点,然后没有边,但是这样边仍然要输出一行。

代码如下:

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

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

using namespace std;

const int MAX = 100100;

struct edge{
    int u,v;
    edge(int uu, int vv):u(uu),v(vv){}
};
int T,N,Q;
int u,v,k;
int head[MAX],nxt[MAX<<2],to[MAX<<2],tot;
char str[5];
int fa[MAX],top[MAX],deep[MAX],num[MAX],p[MAX],fp[MAX],son[MAX];
int pos;
long long ans[MAX];
long long ans1[MAX];

void init()
{
    E.clear();
    tot = pos = 0;
    memset(head,-1,sizeof(head));
    memset(ans,0,sizeof(ans));
    memset(ans1,0,sizeof(ans1));
    memset(son,-1,sizeof(son));
}

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;
    if(son[u] != -1){
        p[u] = pos++;
        fp[p[u]] = u;
        dfs2(son[u],sp);
    }
    else{
        p[u] = pos++;
        fp[p[u]] = u;
        return;
    }
    for(int i = head[u]; i != -1; i = nxt[i]){
        int v = to[i];
        if(v != son[u] && v != fa[u])
            dfs2(v,v);
    }
}

void update(int u, int v,int k)
{
    int f1 = top[u], f2 = top[v];
    while(f1 != f2){
        if(deep[f1] < deep[f2]){
            swap(f1,f2);
            swap(u,v);
        }
        ans[p[f1]] += k, ans[p[u]+1] -= k;
        u = fa[f1]; f1 = top[u];
    }
    if(u == v) return;
    if(deep[u] > deep[v]) swap(u,v);
    ans[p[son[u]]] += k, ans[p[v]+1] -= k;
}

void update1(int u, int v,int k)
{
    int f1 = top[u], f2 = top[v];
    while(f1 != f2){
        if(deep[f1] < deep[f2]){
            swap(f1,f2);
            swap(u,v);
        }
        ans1[p[f1]] += k, ans1[p[u]+1] -= k;
        u = fa[f1]; f1 = top[u];
    }
    if(u == v){
        ans1[p[u]] += k, ans1[p[u]+1] -= k;
        return ;
    };
    if(deep[u] > deep[v]) swap(u,v);
    ans1[p[u]] += k, ans1[p[v]+1] -= k;
}


int main(void)
{
    //freopen("input.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int cas = 1;
    scanf("%d", &T);
    while(T--){
        init();
        scanf("%d %d", &N, &Q);
        for(int i = 0; i < N -1; ++i){
            scanf("%d %d", &u,&v);
            addedge(u,v);
        }
        dfs1(1,0,0);
        dfs2(1,1);
        for(int i = 0; i < Q; ++i){
            scanf("%s %d %d %d",str,&u,&v,&k);
            if(str[3] == '2'){
                update(u,v,k);
            }
            else{
                update1(u,v,k);
            }

        }
        long long sum = 0;
        for(int i = 0; i < pos; ++i){
            sum += ans[i];
            ans[i] = sum;
        }


        for(int i = 0; i < pos; ++i){
            sum += ans1[i];
            ans1[i] = sum;
        }

        printf("Case #%d:\n",cas++);

        for(int i = 1; i <= N; ++i)
            printf("%I64d%c",ans1[p[i]],i == N?'\n':' ');
        for(int i = 0; i < tot-2; i += 2){
            u = to[i+1];
            v = to[i];
            if(fa[u] == v)
                printf("%I64d ",ans[p[u]]);
            else
                printf("%I64d ",ans[p[v]]);
        }
        if(tot != 0){
            u = to[tot-2];v = to[tot-1];
            if(fa[u] == v) printf("%I64d\n",ans[p[u]]);
            else    printf("%I64d\n",ans[p[v]]);
        }
        else puts("");
    }
    return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值