模板 - 树上问题(树的直径、动态查询树的直径、树的重心)

整理的算法模板合集: ACM模板


一、树的直径

树的直径满足如下性质:

  • 若有多条直径,则所有的直径之间皆有公共点。

  • 直径的两端一定是叶子。

  • 树中距离某一直径端点最远的点,至少有一个是该直径的另一个端点。

  • 对于树上任意一个点,与之距离最远的一个点,至少有一个直径的端点。

给你一个无权无向的树。编写程序以输出该树中最长路径(从一个节点到另一个节点)的长度。在这种情况下,路径的长度是我们从开始到目的地的遍历边数。

树形DP

O ( n ) O(n) O(n)

const int N = 50007, M = 500007, INF = 0x3f3f3f3f;

int n, m, F[N], D[N], ans ;
int ver[N], nex[M], head[N], edge[N], tot;
bool vis[N];
void add(int x, int y, int z)
{
    
    
    ver[tot] = y;
    edge[tot] = z;
    nex[tot] = head[x];
    head[x] = tot ++ ;
}

void dp(int x){
    
    
    vis[x] = 1;
    for(int i = head[x] ;~i; i =nex[i]){
    
    
        int y = ver[i], z = edge[i];
        if(vis[y])continue;
        dp(y);
        ans = max(ans, D[x] + D[y] + z);
        D[x] = max(D[x], D[y] + z);
    }
}

int main(){
    
    
    scanf("%d", &n);
    memset(head, -1, sizeof head);
    for(int i = 1; i < n; ++ i){
    
    
        int x, y;
        scanf("%d%d", &x, &y);
        add(x, y, 1), add(y, x, 1);
    }
    dp(1);
    printf("%d\n", ans);
    return 0;
}

两次DFS / BFS(找到直径的两个端点)

O ( n ) O(n) O(n)

//两次dfs一次求P一次求Q
void dfs(int u,int &ed){
    
    
    if(dis[u] > ans)ans = dis[u],ed = u;
    vis[u] = 1;
    for(int i = head[u];~i;i = nex[i]){
    
    
        int v = ver[i],w = edge[i];
        if(vis[v])continue;
        dis[v] = dis[u] + w;
        dfs(v,ed);
    }
    return ;
}
int p,q;
void solve(){
    
    
    dfs(1,p);
    ans = dis[p] = 0;
    memset(vis,0,sizeof vis);
    dfs(p,q);
    cout << ans << endl;
}

在一棵无根树上,找 A , B , C A,B,C A,B,C三个点,使得 A B + B C ( A C > B C ) AB+BC(AC>BC) AB+BC(AC>BC) A C + A B ( B C > A C ) AC+AB(BC>AC) AC+AB(BC>AC)最大。

我们假设 A C > B C AC>BC AC>BC,那么答案就是找到最大 A B + B C AB+BC AB+BC简单贪心一下,先令AB最大,然后再找一个相应的最大的BC,强调要满足(AC>AB)所以我们首先找出树的直径,然后枚举除了两个端点外的点 C C C,使得 m i n ( A C , B C ) min(AC,BC) min(AC,BC)最大,答案就是树的直径 + m i n ( A C , B C ) +min(AC,BC) +min(AC,BC)

typedef long long ll;
const int N = 500007, M = 5000007, INF = 0x3f3f3f3f;

int n, m;
ll d[N], copy_d[N];
bool vis[N];
int head[N], ver[M], nex[M], edge[M], tot;

void add(int x, int y, int z)
{
    
    
    ver[tot] = y;
    edge[tot] = z;
    nex[tot] = head[x];
    head[x] = tot ++ ;
}

int bfs(int S)
{
    
    
    queue<int>q;
    memset(d, 0, sizeof d);
    memset(vis, 0, sizeof vis);
    //while(q.size())q.pop();
    q.push(S), vis[S] = 1;

    int maxx = 0, maxid;
    while(q.size()){
    
    
        int x = q.front();
        q.pop();
        for(int i = head[x]; ~i; i = nex[i]){
    
    
            int y = ver[i], z = edge[i];
            if(vis[y])continue;
            vis[y] = 1;
            d[y] = d[x] + z;
            q.push(y);
            if(d[y] > maxx){
    
    
                maxx = d[y];
                maxid = y;
            }
        }
    }
    return maxid;
}

int main()
{
    
    
    scanf("%d%d", &n, &m);
    memset(head, -1, sizeof head);
    for(int i = 1; i <= m; ++ i){
    
    
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        add(x, y, z);
        add(y, x, z);
    }
    int p, q;
    p = bfs(1);
    q = bfs(p);
    ll ans = d[q];//the length of AB
    for(int i = 1; i <= n; ++ i){
    
    
        copy_d[i] = d[i];
    }
    bfs(q);
    ll res = 0;
    for(int i = 1; i <= n; ++ i){
    
    
        res = max(res, min(d[i], copy_d[i]));//find the longest edges among the remaining edges
    }
    ans += res;
    printf("%lld\n", ans);
    return 0;
}

二、动态修改树的边权并求每个时刻的直径(线段树)

有一个n个点的带权无向树,q次操作,每次修改一条边的权值,要求在每次修改后,输出树的直径大小,强制在线。

/*lmx,rmx,lmi,rmi,ld,rd,lrd,ans前缀/后缀的最大/最小和,前缀右减左最大,
后缀右减左最大,两端点必须选满右减左最大,任意子串右减左最大*/
#include<bits/stdc++.h>
#define ls(a) ((a)<<1)
#define rs(a) ((a)<<1|1)
#define N 200001
#define int long long
using namespace std;
typedef long long ll;
#define G if(++ip==ie)if(fread(ip=ibuf,1,LL,stdin))
const int LL=1<<19;
char ibuf[LL],*ie=ibuf+LL,*ip=ie-1;
inline char nc(void){
    
    G;return *ip;}
//#define getchar nc
inline ll read(void){
    
    
	char opt;ll flag=1,res=0;
	while((opt=getchar())<'0'||opt>'9')if(opt=='-')flag=-1;
	while(opt>='0'&&opt<='9'){
    
    res=(res<<3)+(res<<1)+opt-'0';opt=getchar();}
	return res*flag;
}
int n,m,st[N],top,q;
struct edge{
    
    int to;ll v;int x;};vector<edge>g[N];
ll w,sum[N<<2],lmx[N<<2],rmx[N<<2],lmi[N<<2],rmi[N<<2],ld[N<<2],rd[N<<2],lrd[N<<2],ans[N<<2],lastans,p[N][2];
void pushup(int x){
    
    
	sum[x]=sum[ls(x)]+sum[rs(x)]; 
	lmx[x]=max(lmx[ls(x)],sum[ls(x)]+lmx[rs(x)]); 
	rmx[x]=max(rmx[rs(x)],sum[rs(x)]+rmx[ls(x)]);
	lmi[x]=min(lmi[ls(x)],sum[ls(x)]+lmi[rs(x)]);
	rmi[x]=min(rmi[rs(x)],sum[rs(x)]+rmi[ls(x)]);
	ld[x]=max(ld[ls(x)],max(ld[rs(x)]-sum[ls(x)],lrd[ls(x)]+lmx[rs(x)])); 
	rd[x]=max(rd[rs(x)],max(sum[rs(x)]+rd[ls(x)],lrd[rs(x)]-rmi[ls(x)]));
	lrd[x]=max(lrd[ls(x)]+sum[rs(x)],lrd[rs(x)]-sum[ls(x)]);
	ans[x]=max(max(ans[ls(x)],ans[rs(x)]),max(ld[rs(x)]-rmi[ls(x)],rd[ls(x)]+lmx[rs(x)])); 
}
inline void Build(int pos,int l,int r){
    
    
	ll v;if(l==r)return sum[pos]=(v=st[l]),lmx[pos]=rmx[pos]=max(v,0ll),lmi[pos]=rmi[pos]=min(v,0ll),ld[pos]=rd[pos]=lrd[pos]=ans[pos]=v,void();
	int mid=(l+r)>>1;
	Build(ls(pos),l,mid),Build(rs(pos),mid+1,r);
	pushup(pos);
}
inline void Change(int pos,int l,int r,int x,ll v){
    
    
	if(l==r)return sum[pos]=v,lmx[pos]=rmx[pos]=max(v,0ll),lmi[pos]=rmi[pos]=min(v,0ll),ld[pos]=rd[pos]=lrd[pos]=ans[pos]=v,void();
	int mid=(l+r)>>1;
	(x<=mid)?Change(ls(pos),l,mid,x,v):Change(rs(pos),mid+1,r,x,v);
	pushup(pos);
}
inline void dfs(int x,int prt){
    
    
	int y;for(auto t:g[x])if((y=t.to)^prt)st[*p[t.x]=++top]=t.v,dfs(y,x),st[p[t.x][1]=++top]=-t.v;
}
signed main(void){
    
    
	int i,x;ll y,z;n=read(),q=read(),w=read();
	for(i=1;i<n;++i)x=read(),y=read(),z=read(),g[x].push_back({
    
    y,z,i}),g[y].push_back({
    
    x,z,i});
	dfs(1,0);Build(1,1,top);
	while(q--){
    
    
		x=(read()+lastans)%(n-1)+1,y=(read()+lastans)%w;
		Change(1,1,top,*p[x],y),Change(1,1,top,p[x][1],-y);
		printf("%lld\n",lastans=ans[1]);
	}
    return 0;
}

三、树的重心

重心的性质

  • 以树的重心为根时,所有子树的大小都不超过整棵树大小的一半。
  • 把两棵树通过一条边相连得到一棵新的树,那么新的树的重心在连接原来两棵树的重心的路径上。
  • 在一棵树上添加或删除一个叶子,那么它的重心最多只移动一条边的距离。
  • 树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么到它们的距离和一样。
  • 一棵树最多有两个重心,且相邻。

给定一棵树,求树的重心的编号以及重心删除后得到的最大子树的节点个数size,如果size相同就选取编号最小的.

const int N = 50007, M = 500007, INF = 0x3f3f3f3f;
typedef long long ll;

int n, m;
int head[N], ver[M], nex[M], tot;
ll T;
int son[N], ans, pos;
bool vis[N];

void add(int x, int y)
{
    
    
    ver[tot] = y;
    nex[tot] = head[x];
    head[x] = tot ++ ;
}

void dfs(int x)
{
    
    
    vis[x] = 1, son[x] = 1;
    int res = 0;
    for(int i = head[x]; ~i; i = nex[i]){
    
    
        int y = ver[i];
        if(!vis[y]){
    
    
            dfs(y);
            son[x] += son[y];
            res = max(res, son[y]);//x下面的子树
        }
    }
    res = max(res, n - son[x]);//x上面的子树
    if(res < ans || (res == ans && x < pos)){
    
    
        ans = res, pos = x;
    }
}

int main()
{
    
    
    scanf("%lld", &T);
    while(T -- ){
    
    
        tot = 0;
        memset(head, -1, sizeof head);
        memset(vis, 0, sizeof vis);
        ans = INF;

        scanf("%d", &n);
        for(int i = 1; i < n; ++ i){
    
    
            int x, y;
            scanf("%d%d", &x, &y);
            add(x, y), add(y, x);
        }
        dfs(1);
        printf("%d %d\n", pos, ans);
    }
    return 0;
}


猜你喜欢

转载自blog.csdn.net/weixin_45697774/article/details/108564485