Housewife Wind POJ - 2763 倍增LCA+树状数组 或 树链剖分+线段树

     题目 链接:http://poj.org/problem?id=2763

Language:Default

Housewife Wind

Time Limit: 4000MS Memory Limit: 65536K
Total Submissions: 16100 Accepted: 4388

Description

After their royal wedding, Jiajia and Wind hid away in XX Village, to enjoy their ordinary happy life. People in XX Village lived in beautiful huts. There are some pairs of huts connected by bidirectional roads. We say that huts in the same pair directly connected. XX Village is so special that we can reach any other huts starting from an arbitrary hut. If each road cannot be walked along twice, then the route between every pair is unique. 

Since Jiajia earned enough money, Wind became a housewife. Their children loved to go to other kids, then make a simple call to Wind: 'Mummy, take me home!' 

At different times, the time needed to walk along a road may be different. For example, Wind takes 5 minutes on a road normally, but may take 10 minutes if there is a lovely little dog to play with, or take 3 minutes if there is some unknown strange smell surrounding the road. 

Wind loves her children, so she would like to tell her children the exact time she will spend on the roads. Can you help her? 

Input

The first line contains three integers n, q, s. There are n huts in XX Village, q messages to process, and Wind is currently in hut s. n < 100001 , q < 100001. 

The following n-1 lines each contains three integers a, b and w. That means there is a road directly connecting hut a and b, time required is w. 1<=w<= 10000. 

The following q lines each is one of the following two types: 

Message A: 0 u 
A kid in hut u calls Wind. She should go to hut u from her current position. 
Message B: 1 i w 
The time required for i-th road is changed to w. Note that the time change will not happen when Wind is on her way. The changed can only happen when Wind is staying somewhere, waiting to take the next kid. 

Output

For each message A, print an integer X, the time required to take the next child.

Sample Input

3 3 1
1 2 1
2 3 2
0 2
1 2 3
0 3

Sample Output

1
3

Source

POJ Monthly--2006.02.26,zgl & twb

 

题意:给出一个树,q个询问,s为起点,0 u 询问由目前所在点到u点的距离,1 k val代表修改第k条边的权值为val

解法一:

初学LCA,刚入门的第一题,题目lca+树状数组的思想还是比较好的,记录一下

ps:poj此题G++超时,C++ AC 可能卡STL,建议换成链向星

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=2e5+100;
struct node
{
	int to,val,id;
	node(int a,int b,int c):to(a),val(b),id(c){}
};
vector<node> v[maxn];
int fa[maxn][20],dis[maxn],depth[maxn];
int c[maxn],id[maxn],l[maxn],r[maxn],num,cc[maxn];
int n,s,q;
//lca部分 
void dfs(int u)
{
	l[u]=++num;//dfs序 将树形结构转换成线性结构
	for(int i=0;i<v[u].size();i++)
	{
		node te=v[u][i];
		if(te.to==fa[u][0]) continue;
		fa[te.to][0]=u;
		dis[te.to]=dis[u]+te.val;
		depth[te.to]=depth[u]+1;
		id[te.id]=te.to;//id[i] 代表第i条边对应的离根节点远的节点
		dfs(te.to);
	}
	r[u]=num;
}
void init()
{
	for(int j=1;(1<<j)<=n;j++)
	{
		for(int i=1;i<=n;i++)
			fa[i][j]=fa[fa[i][j-1]][j-1];
	}
}
int lca(int a,int b)
{
	if(depth[a]>depth[b]) swap(a,b);
	int f=depth[b]-depth[a];
	for(int i=0;(1<<i)<=f;i++)
	{
		if((1<<i)&f) b=fa[b][i];
	}
	if(a==b) return a;
	for(int i=19;i>=0;i--)
	{
		if(fa[a][i]!=fa[b][i])
		{
			a=fa[a][i];
			b=fa[b][i];
		}
	}
	return fa[a][0];
}
//树状数组部分 
int lowerbit(int x)
{
	return x&(-x);
} 
void add(int pos,int x)
{
	while(pos<=n)
	{
		cc[pos]+=x;
		pos+=lowerbit(pos);
	}
}
int sum(int pos)
{
	int ans=0;
	while(pos>0)
	{
		ans+=cc[pos];
		pos-=lowerbit(pos);
	 } 
	 return ans;
}

int main()
{
	while(~scanf("%d%d%d",&n,&q,&s))
	{
		memset(v,0,sizeof(v));
		memset(c,0,sizeof(c));
		memset(cc,0,sizeof(cc));
		num=0;
		for(int i=1;i<n;i++)
		{
			int a,b,cc;
			scanf("%d%d%d",&a,&b,&cc);
			v[a].push_back(node(b,cc,i));
			v[b].push_back(node(a,cc,i));
			c[i]=cc;
		}
		fa[1][0]=0,dis[1]=0,depth[1]=0;
		dfs(1);
		init();
		for(int i=1;i<n;i++)//对图中原有的节点进行加边操作 
			add(l[id[i]],c[i]),add(r[id[i]]+1,-c[i]);
		while(q--)
		{  
			int op;
			scanf("%d",&op);
			if(op==0)
			{
				int u;
				scanf("%d",&u);
				//用dfs序代替节点 使得节点出现顺序 改变之前节点以后节点相应受到影响 
				printf("%d\n",sum(l[u])+sum(l[s])-2*sum(l[lca(s,u)])); 		
				s=u;                                                 
			}
			else if(op==1)
			{
				int k,va;
				scanf("%d%d",&k,&va);
				int tmp=c[k];
				c[k]=va;
				add(l[id[k]], va-tmp); add(r[id[k]]+1, tmp-va);//差分区间简单运用
			}
		}
	}
	return 0;
 } 

题意没有太多坑,自己跑下样例应该就能理解了

给组样例:

5 5 1
1 2 3
2 3 4
1 4 2
2 5 1
0 3
1 4 3
1 1 4
0 4
0 5

结果:

7
10
9

总结:题目利用时间戳来代替节点,这样每个节点在树中遍历的顺序体现出来,如果改变v u两节点的对应边的权值,那么到以u为根的子节点长度(根节点到子节点长度)均受到改变(包括u,默认u离根节点远),因此对l[i] 加减 对r[i] 相反操作可以只影响到以u为根节点的子节点的权值,通过求前缀和来计算根节点到此节点的长度(差分区间简单运用,点修改,区间查询用树状数组),再通过lca可以简单求出两点的最短路了

解法二:

树链剖分的模板题,边权的单点修改,以及区间查询

见代码注释:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=2e5+100;
int fa[maxn],top[maxn],depth[maxn],l[maxn],size[maxn],son[maxn];
int tt;
int sum[maxn<<2];
vector<int> v[maxn];
int n,q,s;
int cnt, head[maxn];
struct Edge {
    int to, next;
}edge[maxn];
void add_edge(int f,int t)
{
	edge[cnt].to=t;
	edge[cnt].next=head[f];
	head[f]=cnt++;
}
//预处理部分 
void dfs1(int u,int pre)
{
	size[u]=1;
	for(int i = head[u]; ~i; i= edge[i].next) 
	{
		int te=edge[i].to;
		if(te==pre ) continue;
		depth[te]=depth[u]+1;
		dfs1(te,u);
		fa[te]=u;
		size[u]+=size[te]; 
		if(son[u]==0||size[te]>size[son[u]])
			son[u]=te;
	}
}
void dfs2(int u,int ft)
{
	l[u]=++tt;
	top[u]=ft;
	if(son[u]) dfs2(son[u],top[u]);
	for(int i = head[u]; ~i; i = edge[i].next) {
		int te=edge[i].to;
		if(te==fa[u]||te==son[u]) continue;
		dfs2(te,te);
	}
}
struct node
{
	int f,t,val;
}e[maxn];
//线段树部分 
void update(int p,int val,int l,int r,int rt)
{
	if(l==r){
		sum[rt]=val;
		return ;
	}
	int m=(l+r)>>1;
	if(p<=m) update(p,val,l,m,rt<<1);
	else update(p,val,m+1,r,rt<<1|1);
	sum[rt]=sum[rt<<1]+sum[rt<<1|1]; 
}
int query(int L,int R,int l,int r,int rt)
{
	if(L<=l&&r<=R)
	{
		return sum[rt];
	}
	int m=(l+r)>>1;
	int ans=0;
	if(L<=m) ans+=query(L,R,l,m,rt<<1);
	if(R>m) ans+=query(L,R,m+1,r,rt<<1|1);
	return ans; 
}
//熟练剖分核心 
void change(int k,int val){
	int f=e[k].f,t=e[k].t;
	if(depth[f]>depth[t]) swap(f,t);//单点更新 
	update(l[t],val,1,n,1);
}
int get(int x,int y)
{
	int fx=top[x],fy=top[y];
	int ans=0;
	while(fx!=fy){
		if(depth[fx]>depth[fy]){
			swap(fx,fy);
			swap(x,y);
		}
		ans+=query(l[fy],l[y],1,n,1);
		y=fa[fy];
		fy=top[y];
	}
	if(x==y) return ans;
	if(depth[x]>depth[y]) swap(x,y);
	ans+=query(l[son[x]],l[y],1,n,1);//边权 选取x的重儿子 因为l[x]代表x节点和其父节点的变 直接用x会包括与父节点的边 
	return ans;
}
int main()
{
	scanf("%d%d%d",&n,&q,&s);
	memset(head,-1,sizeof(head));
	cnt=0;
	tt=0;
	for(int i=1;i<n;i++)
	{
		int f,t,val;
		scanf("%d%d%d",&f,&t,&val);
		e[i].f=f;
		e[i].t=t;
		e[i].val=val;
		add_edge(f,t);
		add_edge(t,f);
	}
	fa[1]=0;
	depth[1]=0;
	dfs1(1,0);
	dfs2(1,1);
	for(int i=1;i<n;i++)
	{
		int f=e[i].f,t=e[i].t;
		int val=e[i].val;
		if(depth[f]>depth[t]) swap(f,t);
		update(l[t],val,1,n,1);
	}
	while(q--)
	{
		int op;
		scanf("%d",&op);
		if(op==0){
			int t;
			scanf("%d",&t);
			printf("%d\n",get(s,t)); 
			s=t;
		}
		else
		{
			int k,val;
			scanf("%d%d",&k,&val);
			change(k,val); 
		}
	}
	return 0;
 } 

一点思考:树链剖分很好的解决了树上的区间修改和区间查询问题,本题单点修改用lca+BIT也可以很好的解决

注意:poj卡vector

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值