标签:
题目链接:hdu 5296
题意:在一棵给定的具有边权的树,一个节点的集合S(初始为空),给定Q个操作,每个操作增加或删除S中的一个点,每个操作之后输出使集合S中所有点联通的最小子树的边权和。
思路:最小子树上的节点的充要条件:
- 节点为(S集合中所有点的LCA)的子节点;
- 节点有一个子孙为S集合中的点。
那么我们给每个节点都开一个标记数组,初始为零,每加入一个节点,就把从这个节点到根节点路径上的点的值都+1,反之-1,这样通过对每个单节点值的查询就能知道它是不是最小子树上的节点。
然后对于每一次节点的增加和删除都有两种情况:
第一种:加入或删除新节点不会改变最小子树的根节点,那么就必然存在一个节点是这个新节点的祖先,那么我们找到这个深度最深的祖先在最小子树中的祖先就是关键节点,可以倍增求;
第二种:加入或删除新节点会改变最小子树的根节点,那么这个关键节点就是旧子树的根节点。
求得关键节点之后直接加上或减去两点之间的边的权值和就OK。
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <queue>
#include <set>
#include <algorithm>
#define Lson o<<1,l,mid
#define Rson o<<1|1,mid+1,r
using namespace std;
const int maxn=120010+5;//最大节点数
const int maxm =maxn*2;//最大边条数
const int DEG =20;
int v[maxm];//v[i]表示第i条边指向的节点
int Prev[maxm];//Prev[i]表示i的兄弟边
int ed[maxm][3];//存储的是边的信息
int info[maxn];//表示最后插入的与节点i相连的边
int Q[maxn];//用于实现队列和栈
int idx[maxn];//存储的是节点i在所属链的编号 从叶子到根依次增加
int dep[maxn];//节点的深度
int size[maxn];//链的轻重
int belong[maxn];//表示节点i所属边的编号
int father[maxn];//节点i的直接父亲
bool vis[maxn];//剖分回溯时使用的标记
int head[maxn];//第i条链的头部节点
int len[maxn];//第i条链的长度
int l,r,ans,cnt=0;//cnt为链的条数
int N,nedge=0;//nedge为边的条数 N为总点数
int pos[maxn];//树链剖分后各店的位置
int val[maxn];//求得节点到根节点所经过边的权值和
int rk[maxn];//节点的DFS序
int frk[maxn];//DFS序的反函数
int fa[maxn][DEG];//节点i的2^j倍祖先
/******************树链剖分部分****************************/
//树链剖分部分
inline void insert(int x,int y,int c){
++nedge;
v[nedge]=y;Prev[nedge]=info[x];info[x]=nedge;
ed[nedge][0]=x;ed[nedge][1]=y;ed[nedge][2]=c;
}
void split(){
memset(dep,-1,sizeof(dep));
l=0;
dep[Q[r=1]=1]=0;
father[1]=-1;
while(l<r){
int x=Q[++l];
vis[x]=false;
for(int y=info[x];y!=-1;y=Prev[y]){
if(dep[v[y]]==-1){
dep[Q[++r]=v[y]]=dep[x]+1;
father[v[y]]=x;
}
}
}
for(int i=N;i;i--){
int x=Q[i],p=-1;
size[x]=1;
for(int y=info[x];y!=-1;y=Prev[y]){
if(vis[v[y]]){
size[x]+=size[v[y]];
if(p==-1||size[v[y]]>size[p])
p=v[y];
}
}
if(p==-1){
idx[x]=len[++cnt]=1;
belong[head[cnt]=x]=cnt;
}
else {
idx[x]=++len[belong[x]=belong[p]];
head[belong[x]]=x;
}
vis[x]=true;
}
}
void get_pos(){
int Pos[maxn];
Pos[1]=0;
for(int i=2;i<=cnt;i++)Pos[i]=Pos[i-1]+len[i-1];
for(int i=1;i<=N;i++)pos[i]=Pos[belong[i]]+idx[i];
}
/******************线段树部分****************************/
//区间修改单点查询 也可以使用树状数组
int addv[maxn*4];
void push_down(int o,int l,int r){
if(l==r) return ;
addv[o<<1]+=addv[o];
addv[o<<1|1]+=addv[o];
addv[o]=0;
}
void update(int o,int l,int r,int L,int R,int val){
if(L<=l&&R>=r){
addv[o]+=val;
}
else {
push_down(o,l,r);
int mid=(l+r)>>1;
if(L<=mid)update(Lson,L,R,val);
if(R>mid)update(Rson,L,R,val);
}
}
int quarry(int o,int l,int r,int pos){
if(l==r) return addv[o];
push_down(o,l,r);
int mid=(l+r)>>1;
if(pos>mid)return quarry(Rson,pos);
else return quarry(Lson,pos);
}
/******************剖分后对树的边修改部分****************************/
void change(int a,int b,int val){
int fu=head[belong[a]],fv=head[belong[b]];
while(fu!=fv){
update(1,1,N,pos[b],pos[fv],val);
b=father[fv];
fv=head[belong[b]];
}
update(1,1,N,pos[b],pos[a],val);
}
/*****************BFS求得倍增关系和到根节点边的权值和***************/
void bfs(int root){
for(int i=1;i<=nedge;i++){
if(dep[ed[i][0]]<dep[ed[i][1]])continue;
val[ed[i][0]]=ed[i][2];
}
fa[root][0]=root;
dep[root]=0;
l=r=0;
val[root]=0;
Q[r++]=root;
while(l<r){
int u=Q[l++];
val[u]=val[u]+val[fa[u][0]];
for(int i=1;i<DEG;i++){
fa[u][i]=fa[fa[u][i-1]][i-1];
}
for(int i=info[u];i!=-1;i=Prev[i]){
int drc=v[i];
if(drc==father[u])continue;
dep[drc]=dep[u]+1;
fa[drc][0]=u;
Q[r++]=drc;
}
}
}
/*********************求DFS序及其反函数**************************/
void fdg(int root){
int tt=0;
Q[tt++]=root;
rk[root]=0;
int rr=0;
while(tt>0){
int u=Q[--tt];
rk[u]=rr++;
for(int i=info[u];i!=-1;i=Prev[i]){
int drc=v[i];
if(drc==fa[u][0])continue;
Q[tt++]=drc;
}
}
for(int i=1;i<=N;i++){
frk[rk[i]]=i;
}
}
/*******************倍增法求LCA******************************/
int lca(int a,int b){
if(dep[a]>dep[b])swap(a,b);
int hu=dep[a],hv=dep[b];
int tu=a,tv=b;
for(int i=0,tt=hv-hu;tt;i++,tt/=2){
if(tt&1) tv=fa[tv][i];
}
if(tu==tv) return tu;
for(int i=DEG-1;~i;i--){
if(fa[tu][i]==fa[tv][i])continue;
tu=fa[tu][i];
tv=fa[tv][i];
}
return fa[tu][0];
}
/**********************求当前集合的LCA***********************/
set<int> Set;
set<int>::iterator it;
int get_lca(){
if(Set.empty())
return -1;
if(Set.size()==1){
return frk[*Set.begin()];
}
else {
it=Set.end();
it--;
return lca(frk[*Set.begin()],frk[*it]);
}
}
/*******************倍增法求关键节点*************************/
int get_key_node(int u){
if(quarry(1,1,N,pos[u])) return u;
for(int i=DEG-1;~i;i--){
int tp=quarry(1,1,N,pos[fa[u][i]]);
if(tp)continue;
u=fa[u][i];
}
return fa[u][0];
}
/****初始化****/
void init(){
memset(addv,0,sizeof(addv));
nedge=0;
Set.clear();
cnt=0;
memset(info,-1,sizeof(info));
}
/**********分情况处理询问***************/
void solve(int a,int b){
int lca1,lca2;
int key_node;
if(a==1){
if(Set.find(rk[b])==Set.end()){
lca1=get_lca();
Set.insert(rk[b]);
lca2=get_lca();
if(lca1==-1){
change(1,b,1);
}
else {
if(lca2==lca1){
key_node=get_key_node(b);
ans+=(val[b]-val[key_node]);
}
else {
ans+=(val[b]+val[lca1]-2*val[lca2]);
}
change(1,b,1);
}
}
}
else {
if(Set.find(rk[b])!=Set.end()){
lca1=get_lca();
Set.erase(rk[b]);
lca2=get_lca();
change(1,b,-1);
if(lca2==-1){
}
else {
if(lca1==lca2){
key_node=get_key_node(b);
ans-=(val[b]-val[key_node]);
}
else {
ans-=(val[b]+val[lca2]-2*val[lca1]);
}
}
}
}
}
int main(){
// freopen("100.in","r",stdin);
// freopen("data.out","w",stdout);
int T,M,cas=0;
scanf("%d",&T);
while(T--){
init();
printf("Case #%d:\n",++cas);
scanf("%d%d",&N,&M);
int a,b,c;
for(int i=1;i<N;i++){
scanf("%d%d%d",&a,&b,&c);
insert(a,b,c);
insert(b,a,c);
}
split();
get_pos();
bfs(1);
fdg(1);
ans=0;
while(M--){
scanf("%d%d",&a,&b);
solve(a,b);
printf("%d\n",ans);
}
}
return 0;
}
版权声明:本文为博主原创文章,未经博主允许不得转载。
HDU 5296 Annoying Problem 树链剖分 LCA 倍增法
标签:
原文地址:http://blog.csdn.net/alpc_paul/article/details/47042781