HDU-5044、洛谷P3995 Tree 树上差分 好题

HDU-5044 Tree 树上差分 好题

树上差分 运用 普通差分思想,在树上用一个数组模拟差分,能实现 区间修改 (边和点都可以)和 单点查询 操作,能比线段树快。
既然是模拟一维差分,实现的原理,就是在线段头部+1,线段尾部-1;这样能实现 O(1) 修改, O(n) 查询的功能。

先讲点权的区间修改:
比如 让 点u 到 v 之间路径上所有点+1,想象以 u-v 路径上深度最小的点(即最近公共祖先 lca ) 把线段分成两段,这样一条线段就分成了两个线段,这两个线段分别处于左子树和右子树;那么只需要让tree[u]+=1,tree[v]+=1,同时让tree[ lca(u,v) ] - = 1,
tree[ fa[ lca(u,v) ] ] - = 1;学过差分的同学应该容易想象,一个结点其子树包含自身的tree[ ]值和即为当前结点修改后的最终值,可以用dfs遍历的方式实现。

ll lca(ll x,ll y){
	while(top[x]!=top[y]){
		if(dep[top[x]]>dep[top[y]])swap(x,y);
		y=fa[top[y]];
	}
	if(dep[x]>dep[y])swap(x,y);
	return x;
}
void update1(ll x,ll y,ll k){
	tree1[x]+=k;tree1[y]+=k;
	tree1[lca(x,y)]-=k;
	tree1[fa[lca(x,y)]]-=k; 
} 
void sum1(ll x,ll pre){
	for(int i=head[x];i!=-1;i=z[i].next){
		if(z[i].e==pre)continue;
		sum1(z[i].e,x);
		tree1[x]+=tree1[z[i].e];
	}
}

再讲边权的区间修改:
这里的处理方式采用了,把边权下放给点权的方式,统一把边权给 这条边连接的两个点中 深度较大的点。
这样在差分的时候,只要令tree[ u ]+=1,tree[ v ]+=1;同时让
tree[ lca(u,v) ] - = 2;不难想象,在一次修改中,让 u-v 这条链上除了 lca 的所有点+1;结合点权的实际意义,lca 的值代表连接 lca 与其父亲这条边的边权,是不包含在 u-v 链中的。如此看来上面的操作正好实现了这个功能。

ll lca(ll x,ll y){
	while(top[x]!=top[y]){
		if(dep[top[x]]>dep[top[y]])swap(x,y);
		y=fa[top[y]];
	}
	if(dep[x]>dep[y])swap(x,y);
	return x;
}
void update2(ll x,ll y,ll k){
	tree2[x]+=k;tree2[y]+=k;
	tree2[lca(x,y)]-=2*k;
}
void sum2(ll x,ll pre){
	for(int i=head[x];i!=-1;i=z[i].next){
		if(z[i].e==pre)continue;
		sum2(z[i].e,x);
		tree2[x]+=tree2[z[i].e];
	}
}

这里给出例题 HDU-5044 是一道需要用树上差分同时维护 点权和边权的题目。题目大致意思是,有两种操作:1.令 u-v 链上所有结点的权值 +k; 2.令 u-v 链上所有边的权值 +k;

这里给出我的AC代码:

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#include <iostream>
#pragma comment(linker, "/STACK:102400000,102400000")//此处采用手动扩栈,不然会T 呜呜呜~ 
using namespace std;
#define ll long long
#define pb push_back
#define rep(i,j,k) for(ll i=j;i<=k;i++)
#define per(i,j,k) for(ll i=j;i>=k;i--)
const ll mod=998244353;
const ll maxn=1e6+10;
ll qp(ll x,ll y){
	ll ans=1;
	while(y){
		if(y%2)ans=ans*x%mod;x=x*x%mod;y/=2;}return ans;}
inline ll qread(){
    ll s=0,w=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
    for (;ch>='0'&&ch<='9';ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
    return (w==-1?-s:s);}

struct node{
	ll s,e,next;
}z[2*maxn];

ll n,m,len,cnt,num=1;
ll w[maxn],son[maxn],top[maxn],fa[maxn],dep[maxn],siz[maxn];
ll tree1[maxn],tree2[maxn],head[maxn];
void add(ll a,ll b){
	z[len].s=a;z[len].e=b;z[len].next=head[a];head[a]=len++;
}
void dfs1(ll x,ll pre){
	fa[x]=pre;dep[x]=dep[pre]+1;son[x]=0;
	for(int i=head[x];i!=-1;i=z[i].next){
		if(z[i].e==pre)continue;
		dfs1(z[i].e,x);
		siz[x]+=siz[z[i].e];
		if(siz[z[i].e]>=siz[son[x]])son[x]=z[i].e;
	}
}
void dfs2(ll x,ll pre){
	if(son[pre]==x)top[x]=top[pre];
	else top[x]=x;
	if(son[x])dfs2(son[x],x);
	for(int i=head[x];i!=-1;i=z[i].next){
		if(z[i].e==pre)continue;
		if(z[i].e==son[x])continue;
		dfs2(z[i].e,x);
	}
}
ll lca(ll x,ll y){
	while(top[x]!=top[y]){
		if(dep[top[x]]>dep[top[y]])swap(x,y);
		y=fa[top[y]];
	}
	if(dep[x]>dep[y])swap(x,y);
	return x;
}
void update1(ll x,ll y,ll k){
	tree1[x]+=k;tree1[y]+=k;
	tree1[lca(x,y)]-=k;
	tree1[fa[lca(x,y)]]-=k; 
} 
void update2(ll x,ll y,ll k){
	tree2[x]+=k;tree2[y]+=k;
	tree2[lca(x,y)]-=2*k;
}
void sum1(ll x,ll pre){
	for(int i=head[x];i!=-1;i=z[i].next){
		if(z[i].e==pre)continue;
		sum1(z[i].e,x);
		tree1[x]+=tree1[z[i].e];
	}
}
void sum2(ll x,ll pre){
	for(int i=head[x];i!=-1;i=z[i].next){
		if(z[i].e==pre)continue;
		sum2(z[i].e,x);
		tree2[x]+=tree2[z[i].e];
	}
}
int main(){
	ll t;
	scanf("%lld",&t);
	while(t--){
		printf("Case #%lld:\n",num++);
		len=0,cnt=0;
		scanf("%lld%lld",&n,&m);
		memset(head,-1,sizeof(head));
		rep(i,1,n-1){
			ll a,b;
			scanf("%lld%lld",&a,&b); 
			add(a,b);add(b,a);
			siz[i]=1;
		}
		siz[n]=1;
		dfs1(1,0);
		dfs2(1,0);
		memset(tree1,0,sizeof(tree1));
		memset(tree2,0,sizeof(tree2));
		rep(i,1,m){
			char op[10];
			scanf("%s",op);
			ll a,b,c;
			scanf("%lld%lld%lld",&a,&b,&c);
			if(op[3]=='1'){
				update1(a,b,c);
			}else{
				update2(a,b,c);
			} 
		}
		sum1(1,0);sum2(1,0);
		rep(i,1,n-1)printf("%lld ",tree1[i]);
		printf("%lld\n",tree1[n]);
		for(int i=0;i<len-2;i+=2){
			ll a=z[i].e;ll b=z[i].s;
			if(dep[a]>dep[b])swap(a,b);
			printf("%lld ",tree2[b]);
		}
		if(len!=0){
			ll a=z[len-2].e,b=z[len-2].s;
			if(dep[a]>dep[b])swap(a,b);
			printf("%lld",tree2[b]);
		}
		printf("\n");
	}
    return 0;
}

我的代码 4020ms 卡过的…亲测,这道题用线段树会T到死,然后开个手动扩栈,尽量少用数组初始化,然后这道题还卡了输出格式…很坑就对了0.0

洛谷P3995 树链剖分(伪模板)

简单讲解下题意, 已知 普通的树链剖分是以子树节点的个数为依据(即 siz【】)划分一个节点的中儿子与重链,题目会给出 1e5 个节点与 2e5 个询问,我们需要以一个新的法则来划分重链,使得对于所有询问的 从节点U到节点V的路径中轻链和重链的总数和 sum 最小。

这道题的第一想法就是贪心,令所有询问中,经过边数最多的边作为一条重链边。那么问题就转化为了找节点U到节点V之间的路径,以及使路径中所有的节点权值加1。这个功能怎么实现呢,找两点间的路径,我选择使用树链剖分的 top 数组来找到两点的LCA;至于区间修改嘛,我用的是树上差分,感觉用线段树可以的~~吧。
但是直接的树链剖分只能得82分,亲测。问题出在区间修改上。首先我们维护的是边权:
正常操作是:

	tree1[x]++,tree1[y]++,tree1[lca]-=2;

但是如果fa【U】||fa【V】==LCA;那么这条边不论是不是重链,都只会经过一次,那么就可以不用操作,避免影响最优解;

同时在最后一次dfs中,判断条件要变为: <=

	if(tree1[son[x]]<=tree1[t])son[x]=t;

如果用<那么,就有可能出现 非叶子节点没有重儿子的情况;

附上代码:

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<algorithm>
using namespace std;
#define ll long long
#define rep(i,j,k) for(ll i=j;i<=k;i++)
#define per(i,j,k) for(ll i=j;i>=k;i--)
const ll mod=1e9+7;
const ll maxn=1e6+5;
ll qp(ll x,ll y){
	ll ans=1;
	if(y<0)return 0;
	while(y){
		if(y%2)ans=ans*x%mod;x=x*x%mod;y/=2;}return ans;}
inline ll qread(){
    ll s=0,w=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
    for (;ch>='0'&&ch<='9';ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
    return (w==-1?-s:s);}

ll n,m,len=0;
ll siz[maxn],dep[maxn],fa[maxn],top[maxn]={0},son[maxn]={0};
ll head[maxn];
ll tree1[maxn]={0};
struct node{
	ll s,e,next;
}z[maxn];
void add(ll a,ll b){
	z[len].s=a;z[len].e=b;z[len].next=head[a];head[a]=len++;
}
void dfs1(ll x,ll pre){
	fa[x]=pre;dep[x]=dep[pre]+1;tree1[x]=0;
	son[x]=0; 
	for(int i=head[x];i!=-1;i=z[i].next){
		ll t=z[i].e;
		if(t==pre)continue;
		dfs1(t,x);
		siz[x]+=siz[t];
		if(siz[t]>siz[son[x]])son[x]=t;
	}
}
void dfs2(ll x,ll pre){
	if(son[pre]==x)top[x]=top[pre];
	else top[x]=x;
	if(son[x])dfs2(son[x],x);
	for(int i=head[x];i!=-1;i=z[i].next){
		ll t=z[i].e;
		if(t==pre)continue;
		if(t==son[x])continue;
		dfs2(t,x);
	}
}
ll lca(ll x,ll y){
	while(top[x]!=top[y]){
		if(dep[top[x]]>dep[top[y]])swap(x,y);
		y=fa[top[y]];
	}
	if(dep[x]>dep[y])swap(x,y);
	return x;
}
void update1(ll x,ll y){
	ll w=lca(x,y);
	if(fa[x]!=w)tree1[x]++,tree1[w]--;
	if(fa[y]!=w)tree1[y]++,tree1[w]--;
}
void sum1(ll x,ll pre){
	son[x]=0;
	for(int i=head[x];i!=-1;i=z[i].next){
		ll t=z[i].e;
		if(t==pre)continue;
		sum1(t,x);
		tree1[x]+=tree1[t];
		if(tree1[son[x]]<=tree1[t])son[x]=t;
	}
}
int main(){
	n=qread(),m=qread();
	memset(head,-1,sizeof(head));
	rep(i,1,n-1){
		ll a,b;
		scanf("%lld%lld",&a,&b); 
		add(a,b);add(b,a);
		siz[i]=1;
	}
	siz[n]=1;
	dfs1(1,0);dfs2(1,0);
	for(int i=1;i<=m;i++){
		ll a=qread(),b=qread();
		update1(a,b);
	}
	sum1(1,0);
	for(int i=1;i<=n;i++){
		printf("%lld\n",son[i]);
	}
	return 0;
}

我试过用vector存边,但是只能拿82分,但是用链式前向星就100 了,这里存疑。
对于链式前向星和vector的区别,我也不是很明白,之前做到过一道题目,用 vector 就是会T,但是链式前向星就过了,吸取教训,以后都用链式前向星存边吧。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值