1、多次询问求树上两个点的距离
例题:hdu2586 http://acm.hdu.edu.cn/showproblem.php?pid=2586
分析:LCA的板子题,关键利用dist[u,v]=dist[1,u]+dist[1,v]-2*dist[1,lca(u,v)];
Ac code:
#include<bits/stdc++.h>
using namespace std;
const int maxn=4e4+10;
typedef long long ll;
int head[maxn],tot,T;
struct Edge
{
int v,w,nxt;
} edge[maxn<<1];
void addedge(int u,int v,int w)
{
edge[tot].v=v;
edge[tot].w=w;
edge[tot].nxt=head[u];
head[u]=tot++;
edge[tot].v=u;
edge[tot].w=w;
edge[tot].nxt=head[v];
head[v]=tot++;
}
int f[maxn][20],d[maxn];
ll dist[maxn];
void dfs(int u,int fa)///预处理
{
for(int i=head[u];~i;i=edge[i].nxt)
{
int v=edge[i].v;
if(v==fa) continue;
d[v]=d[u]+1;
dist[v]=dist[u]+edge[i].w;
f[v][0]=u;
for(int j=1;j<=T;j++)
f[v][j]=f[f[v][j-1]][j-1];
dfs(v,u);
}
}
int lca(int x,int y)///logn求x,y的lca
{
if(d[x]>d[y]) swap(x,y);
for(int i=T;i>=0;i--)
if(d[f[y][i]]>=d[x]) y=f[y][i];
if(x==y) return x;
for(int i=T;i>=0;i--)
if(f[y][i]!=f[x][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
int main()
{
int n,t,m;
scanf("%d",&t);
int u,v,w;
while(t--)
{
memset(head,-1,sizeof head);
memset(d,0,sizeof d);
memset(dist,0,sizeof dist);
scanf("%d%d",&n,&m);
T=log(n)/log(2)+1;
for(int i=1; i<=n-1; i++)
{
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w);
}
dfs(1,-1);
while(m--){
scanf("%d%d",&u,&v);
printf("%lld\n",dist[u]+dist[v]-2*dist[lca(u,v)]);
}
}
return 0;
}
例题:POJ3417 http://poj.org/problem?id=3417
分析:
1、“主要边”构成一棵树,“附加边”则是非树边。把一条附加边(x,y)添加到主要边构成的树中,会与树上x,y之间的路径一起形成一个环。如果第一步选择切断x,y之间 路径上的某条边,那么第二步就必须切断附加边(x,y),才能令Dark被斩为不联通的两部分。
2、我们称每条附加边(x,y)都把树上x,y之间的路径上的每条边“覆盖了一次"。我们只需统计出每条”主要边“被覆盖了多少次,若第一步把被覆盖0次的主要边切断,则第二步可任意选一条附加边切断。若第一步把被覆盖了一次的主要边切断,则第二步方法唯一。若第一步把被覆盖了两次及以上的主要边切断,则如何操作都不能击败Dark。
Ac code:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
using namespace std;
const int maxn=1e5+10;
typedef long long ll;
struct Edge{
int v,w,nxt;
}edge[maxn<<1];
int head[maxn],tot;
void addedge(int u,int v,int w)
{
edge[tot].v=v;
edge[tot].w=w;
edge[tot].nxt=head[u];
head[u]=tot++;
}
int F[maxn],val[maxn];
int f[maxn][20],d[maxn],T;
void dfs1(int u,int fa)
{
for(int i=head[u];~i;i=edge[i].nxt){
int v=edge[i].v;
if(v==fa) continue;
d[v]=d[u]+1;
f[v][0]=u;
for(int j=1;j<=T;j++)
f[v][j]=f[f[v][j-1]][j-1];
dfs1(v,u);
}
}
int lca(int x,int y)
{
if(d[x]>d[y]) swap(x,y);
for(int i=T;i>=0;i--)
if(d[f[y][i]]>=d[x]) y=f[y][i];
if(x==y) return x;
for(int i=T;i>=0;i--)
if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
void dfs2(int u,int fa)
{
F[u]=val[u];
for(int i=head[u];~i;i=edge[i].nxt){
int v=edge[i].v;
if(v==fa) continue;
dfs2(v,u);
F[u]+=F[v];
}
}
int main()
{
int n,m,u,v;
memset(head,-1,sizeof head);
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
addedge(u,v,1);
addedge(v,u,1);
}
T=(int)(log(n)/log(2))+1;
d[1]=1;
dfs1(1,-1);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&u,&v);
val[u]++,val[v]++;
val[lca(u,v)]-=2;
}
dfs2(1,-1);
ll ans=0;
for(int i=1;i<=n;i++)
if(F[i]==0&&i!=1)
ans+=m;
else if(F[i]==1)
ans++;
printf("%lld\n",ans);
return 0;
}
3、树上差分
分析:支持三种操作
1、修改路径上的结点值
2、查询单个结点权值
3、查询一个结点子树的权值和
对于操作1,显然用树上差分,用val[i]表示结点i的差分数组值,则i结点的真正改变值为以i为子树的所有结点的val数组和,如要修改(x,y)路径上的所有结点值+z,则val[x]+=z,val[y]+=z,val[lca(x,y)]-=z,val[fa[lca(x,y)]-=z;类似区间上的差分操作,对于结点fa[lca(x,y)](表示x,y的最近公共祖先的父节点)以上的结点的子树的val和都是不变的,只有[x,y]路径上的结点的子树val和加上了z,不在该路径上的都没变。
操作2,单点权值=差分数组前缀和+原本值,树状数组即可实现
操作3,结点i的子树的权值和与结点深度有关,维护关于结点深度*修改值的树状数组(具体看代码)...
Ac code:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;;
typedef long long ll;
ll a[maxn];
template<class T>
void read(T &x) {
x = 0;
int f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
x=x * f;
}
int tot,n,cnt;
struct Edge{
int v,nxt;
}edge[maxn<<1];
int head[maxn];
int d[maxn],T;
int fa[maxn],dfn[maxn];///dfn为每个结点dfs序的编号
ll sum[maxn];
int sze[maxn],top[maxn],son[maxn];
void init()
{
tot=0;
cnt=0;
memset(head,-1,sizeof head);
T=log(n)/log(2)+1;
}
struct Tree{
ll c[maxn<<1];
int lowbit(int x)
{
return x&(-x);
}
void update(int x,ll val)
{
while(x<=cnt){
c[x]+=val;
x+=lowbit(x);
}
}
ll getsum(int x)
{
ll ans=0;
while(x>0){
ans+=c[x];
x-=lowbit(x);
}
return ans;
}
ll query(int u){
return getsum(dfn[u]+sze[u]-1)-getsum(dfn[u]-1);
}
}A,B;
void change(int u,ll val)
{
A.update(dfn[u],val);
B.update(dfn[u],val*d[u]);
}
void addedge(int u,int v)
{
edge[tot].v=v;
edge[tot].nxt=head[u];
head[u]=tot++;
}
void dfs1(int u,int faa)
{
dfn[u]=++cnt;
sze[u]=1;
for(int i=head[u];~i;i=edge[i].nxt){
int v=edge[i].v;
if(v==faa) continue;
d[v]=d[u]+1;
fa[v]=u;
son[u]=v;
dfs1(v,u);
sze[u]+=sze[v];
sum[u]+=sum[v];
if(sze[v]>sze[son[u]]) son[u]=v;
}
}
//int lca(int x,int y)
//{
// if(d[x]>d[y]) swap(x,y);
// for(int i=T;i>=0;--i)
// if(d[f[y][i]]>=d[x]) y=f[y][i];
// if(x==y) return x;
// for(int i=T;i>=0;--i)
// if(f[y][i]!=f[x][i]) x=f[x][i],y=f[y][i];
// return f[x][0];
//}
///树链剖分求LCA
void DFS(int u, int TOP) {
top[u] = TOP;
if (son[u])
DFS(son[u], TOP);
for (int i = head[u]; ~i; i =edge[i].nxt)
if (!top[edge[i].v])
DFS(edge[i].v, edge[i].v);
}
int lca(int x, int y) {
while (top[x] != top[y])
if (d[top[x]] > d[top[y]])
x = fa[top[x]];
else
y = fa[top[y]];
return d[x] < d[y] ? x : y;
}
int main()
{
int m,r;
read(n),read(m),read(r);
init();
for(int i=1;i<=n;i++)
read(a[i]),sum[i]=a[i];
int u,v;
for(int i=1;i<=n-1;i++){
read(u),read(v);
addedge(u,v);
addedge(v,u);
}
dfn[0]=++cnt;///0号下标树状数组不能用,故要去掉
d[r]=1;
dfs1(r,0);
DFS(r,r);
int x,y,op;
ll z;
while(m--){
read(op),read(x);
if(op==1){
read(y),read(z);
int anc=lca(x,y);
change(x,z);
change(y,z);
change(anc,-z);
change(fa[anc],-z);
}
else if(op==2){
printf("%lld\n",A.query(x)+a[x]);
}
else{
printf("%lld\n",B.query(x)-A.query(x)*(d[x]-1)+sum[x]);
}
}
return 0;
}