题目 链接:http://poj.org/problem?id=2763
Language:Default Housewife Wind
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. 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. Output For each message A, print an integer X, the time required to take the next child. Sample Input Sample Output 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