hdu 5296 lca+dfs应用,lca倍增法模板

题目大意:给出一棵树,每个边都有一个权值,现在有一个空的集合,两种操作,1 x吧x节点放到集合中(如果还没放入),2 x把x节点从集合中拿出来(已放入)。每次操作后输出最小的边权和,保证这些边可以将这些点连起来。
首先明确一点的是,两个点x,y最短的路径肯定是经过两点的lca的,而新添一个点u之后,也就是这个点要连到这条链上去的话,是有下面的公式的:
首先是u到x的距离,dis[u]+dis[x]-2*dis[lca(x,u)]
u到y的距离,dis[u]+dis[x]-2*dis[lca(x,u)]
x到y的距离,dis[x]+dis[y]-2*dis[lca(x,y)]
u到这条链的距离就是u到x的距离+u到y的距离-x到y的距离,结果除以2.
那么我们知道了边权和是怎么增加的了,下面就是要找该点u究竟连到那条链上能最短。
题解上用到了dfs序,让我证明我不会,但是画图的话是能够明白这种做法是正确的。即如果u点dfs序被集合点的dfs序夹住了,那么找最靠近u的两个点,恩,是dfs序最靠近u的。如果没被夹住,就找集合的最大和最小的dfs序对应的点。套上面的公式即可。
不明白怎么回事的自己画棵树比划比划就行吧。
自己写的模板太烂了,直接爆栈了,网上找了一模板自己又写了一边,屯一下。思路模板都不是我的,真是弱爆了。
代码:
//3024ms lca倍增法 
#include<iostream>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<set>
#include<queue>
#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;

const int maxn=100005;
struct node{
    int v,next,c;
};
node edge[maxn<<1];
int head[maxn],cnt;
void add(int a,int b,int c){
    edge[cnt].v=b;
    edge[cnt].c=c;
    edge[cnt].next=head[a];
    head[a]=cnt++;
}
int belong[maxn];
int first[maxn];//第i个节点最早出现的时候
int time1;
int vis[maxn];
set<int>s;
set<int>::iterator it,itx;
int dis[maxn], fa[maxn][20], dep[maxn];
void bfs(int root) {
 queue<int> q;
 fa[root][0] = root;dep[root] = 0;dis[root] = 0;
 q.push(root);
 while (!q.empty()) {
  int u = q.front();q.pop();
  for (int i = 1;i<20;i++)fa[u][i] = fa[fa[u][i - 1]][i - 1];
  for (int i = head[u]; ~i;i = edge[i].next) {
   int v = edge[i].v;if (v == fa[u][0])continue;
   dep[v] = dep[u] + 1;dis[v] = dis[u] + edge[i].c;fa[v][0] = u;
   q.push(v);
  }
 }
}
int Lca(int x, int y) {
 if (dep[x]<dep[y])swap(x, y);
 for (int i = 0;i<20;i++)if ((dep[x] - dep[y])&(1 << i))x = fa[x][i];
 if (x == y)return x;
 for (int i = 19;i >= 0;i--)if (fa[x][i] != fa[y][i])x = fa[x][i], y = fa[y][i];
 return fa[x][0];
}
void dfs(int u, int fa) {
 belong[u] = ++time1;
 first[time1] = u;
 for (int i(head[u]); ~i; i = edge[i].next) {
  int v = edge[i].v; if (v == fa)continue;
  dfs(v, u);
 }
}

void init(){
    memset(head,-1,sizeof(head));
    cnt=0;
    memset(first,0,sizeof(first));
    memset(vis,0,sizeof(vis));
}

int solve(int u){
if (s.empty())
  return 0;
 int x, y;
   set<int>::iterator it = s.lower_bound(u), itx = it;
 itx--;
 if (it == s.end() || it == s.begin()) {
  it = s.begin();
  itx = s.end();
  itx--;
 }
 y = (*it);
 x = (*itx);
 y = first[y];
 x =first[x];
 u=first[u];
    return dis[u]-dis[Lca(u,x)]-dis[Lca(u,y)]+dis[Lca(x,y)];
}

int main(){
    int n,q;
    int u,v,d;
    int T;
    int cas=1;
    scanf("%d",&T);
    while(T--){
            scanf("%d%d",&n,&q);
            init();
            s.clear();
            for(int i=1;i<n;i++){
                scanf("%d%d%d",&u,&v,&d);
                add(u,v,d);
                add(v,u,d);
            }
            bfs(1);
            time1=0;
            dfs(1,1);
            int ans=0;
            printf("Case #%d:\n",cas++);
            for(int i=1;i<=q;i++){
                scanf("%d%d",&u,&v);
                v=belong[v];
                if(u==1){
                    if(!vis[v]){
                             vis[v]=1;
                        if(s.size()==0){
                            s.insert(v);
                        }
                        else{
                            ans+=solve(v);
                            s.insert(v);
                        }
                    }
                }
                else{
                        if(vis[v]){
                                vis[v]=0;
                                s.erase(v);
                                if(s.size()!=0){
                                    ans-=solve(v);
                                }
                        }
                }
                printf("%d\n",ans);
            }
    }
return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值