树的直径习题

Building Fire Stations ZOJ-3820

链接

题意:给定一颗无根树让你选择两个点,让其他所有点到距离最近的选定点的最大距离最小,输出选定的两个点和这个最大的距离
思路

  • 方法一:如果只有一个点,那么答案就是树直径上的中点,现在要选取两个点,可以类比于把一个圆变成了两个圆。那么就按照中点,把这棵树分成两个连通块,然后分别取它们直径的中点,就是答案。
  • 方法二:二分。先二分出最小的最大距离,首先找到直径的一端 u 1 u_1 u1 。从这个 u 1 u_1 u1 开始往回走 k 步,到达祖先 v 1 v_1 v1 ,以这个 v 1 v_1 v1 做半径的为 k 的bfs。然后在图上找另一个没有访问过的且距离最远的点 u 2 u_2 u2,也是往回走 k 步到达 v 2 v_2 v2,然后以 v 2 v_2 v2 做半径为k的bfs。最后如果判断出图上所有点都被访问了,那么这个二分的距离 k 就是合法的。

方法1

#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#define ll long long
using namespace std;
const int maxn=2e5+10,maxm=500+10;
int t,n;
vector<int> e[maxn];
int depth[maxn],fa[maxn];
int get_far(int s)
{
    int far=0;
    depth[s]=0;
    fa[s]=-1;
    queue<int> q;
    q.push(s);
    while(!q.empty())
    {
        int u=q.front();q.pop();
        far=u;
        for(auto v: e[u])
        {
            if(v==fa[u]) continue;
            depth[v]=depth[u]+1;
            fa[v]=u;
            q.push(v);
        }
    }
    return far;
}
int get_center(int x)
{
    int far=get_far(x);
    far=get_far(far);
    int t=depth[far]/2;
    while(t--) far=fa[far];
    return far;
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;++i) e[i].clear();
        for(int i=1;i<=n-1;++i)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            e[u].push_back(v);
            e[v].push_back(u);
        }
        int center=get_center(1);
        int father=fa[center];
        for(int i=0;i<e[center].size();++i)
        {
            if(e[center][i]==father)
            {
                e[center].erase(e[center].begin()+i);
                break;
            }
        }
        for(int i=0;i<e[father].size();++i)
        {
            if(e[father][i]==center)
            {
                e[father].erase(e[father].begin()+i);
                break;
            }
        }
        int p1=get_center(fa[center]);
        int dis1=depth[p1];
        int p2=get_center(center);
        int dis2=depth[p2];
        int dis=max(dis1,dis2);
        printf("%d %d %d\n",dis,p1,p2);
    }
    return 0;
}

方法2

#include <bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;

int t,n,far,p1,p2;
vector<int> e[maxn];
bool visit[maxn][2];
int fa[maxn],depth[maxn],dis[maxn];;

int get_far(int s)
{
	int far=0;
    depth[s]=0;
    fa[s]=-1;
    queue<int> q;
    q.push(s);
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        far=u;
        for(auto v: e[u])
        {
            if(v==fa[u]) continue;
            depth[v]=depth[u]+1;
            fa[v]=u;
            q.push(v);
        }
    }
    return far;
}
int movek(int u,int k)
{
    while(k--&&u!=-1)
        u=fa[u];
    return u;
}
void bfs(int s,int k,int t)
{
    dis[s]=0;
    queue<int> q;
    q.push(s);
    visit[s][t]=1;
    while(!q.empty())
    {
        int u=q.front();q.pop();
        if(dis[u]==k) continue;
        for(auto v: e[u])
        {
            if(visit[v][t]) continue;
            visit[v][t]=1;
            dis[v]=dis[u]+1;
            q.push(v);
        }
    }
}
bool check(int k)
{
    p1=p2=-1;
    memset(visit,0,sizeof(visit));
    int v1=movek(far,k);
    if(v1==-1)
    {
        p1=1,p2=2;
        return 1;
    }
    p1=v1;
    bfs(v1,k,0);
    int u2=-1;
    for(int i=1;i<=n;++i)
    {
        if(visit[i][0]) continue;
        if(u2==-1||depth[i]>depth[u2]) u2=i;
    }
    if(u2==-1)
    {
        p1=v1;
        if(v1+1<=n) p2=v1+1;
        else p2=v1-1;
        return 1;
    }
    int v2=movek(u2,k);
    if(v2==-1)
    {
        if(p1==1) p2=2;
        else p2=1;
        return 1;
    }
    bfs(v2,k,1);
    p2=v2;
    for(int i=1;i<=n;++i)
        if(!visit[i][0]&&!visit[i][1])
            return 0;
    return 1;
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;++i) e[i].clear();
        for(int i=1;i<=n-1;++i)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            e[u].push_back(v);
            e[v].push_back(u);
        }
        far=get_far(1);
        int l=0,r=n;
        while(l<r)
        {
            int mid=(l+r)>>1;
            if(check(mid)) r=mid;
            else l=mid+1;
        }
        check(l);
        printf("%d %d %d\n",l,p1,p2);
    }
    return 0;
}

Diameter of Tree

链接:https://www.codechef.com/problems/DTREE
题意:给定一颗树,请你求出删除每一个节点之后的森林中的最大直径是多少 ( 1 ≤ n ≤ 100000 ) (1 \le n\le 100000) 1n100000
思路

#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#define ll long long
using namespace std;
const int maxn=1e5+10,maxm=500+10;

int t,n;
int depth[maxn];//子树中最大深度
int dis1[maxn];//与自己相连的最远距离
int dis2[maxn];//子树中的最远+次远:最大子树直径
int ans[maxn];
vector<int> e[maxn];

void dfs1(int u,int fa=-1)
{
    for(auto v: e[u])
    {
        if(v==fa) continue;
        dfs1(v,u);
        depth[u]=max(depth[u],depth[v]+1);
    }
}
void dfs2(int u,int fa=-1)
{
    int mx1=-1,mx2=-1;
    for(auto v: e[u])
    {
        if(v==fa) continue;
        dis1[v]=dis1[u]+1;
        if(mx1<=depth[v])
        {
            mx2=mx1;
            mx1=depth[v];
        }
        else mx2=max(mx2,depth[v]);
    }

    for(auto v: e[u])
    {
        if(v==fa) continue;
        if(mx1==depth[v]) dis1[v]=max(dis1[v],mx2+2);
        else dis1[v]=max(dis1[v],mx1+2);
        dfs2(v,u);
        dis2[u]=max(dis2[u],dis2[v]);
    }
    dis2[u]=max(dis2[u],mx1+mx2+2);
}
void dfs3(int u,int fa=-1,int p=0)
{
    int mx1=-1,mx2=-1,mx3=-1,mxd1=0,mxd2=0;
    for(auto v: e[u])
    {
        if(v==fa) continue;
        if(mx1<=depth[v])
        {
            mx3=mx2,mx2=mx1;
            mx1=depth[v];
        }
        else if(mx2<=depth[v])
        {
            mx3=mx2;
            mx2=depth[v];
        }
        else mx3=max(mx3,depth[v]);

        if(mxd1<=dis2[v])
        {
            mxd2=mxd1;
            mxd1=dis2[v];
        }
        else mxd2=max(mxd2,dis2[v]);
    }
    ans[u]=max(ans[u],mxd1);
    for(auto v: e[u])
    {
        if(v==fa) continue;
        int mx=p;
        if(mxd1==dis2[v]) mx=max(mx,mxd2);
        else mx=max(mx,mxd1);
        if(depth[v]==mx1)
        {
            mx=max(mx,mx2+mx3+2);
            mx=max(mx,mx2+1+dis1[u]);
        }
        else if(depth[v]==mx2)
        {
            mx=max(mx,mx1+mx3+2);
            mx=max(mx,mx1+1+dis1[u]);
        }
        else
        {
            mx=max(mx,mx1+mx2+2);
            mx=max(mx,mx1+1+dis1[u]);
        }
        ans[v]=max(ans[v],mx);
        dfs3(v,u,mx);
    }
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;++i)
        {
            depth[i]=dis1[i]=dis2[i]=ans[i]=0;
            e[i].clear();
        }
        for(int i=1;i<=n-1;++i)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            e[u].push_back(v);
            e[v].push_back(u);
        }
        dfs1(1);
        dfs2(1);
        dfs3(1);
        for(int i=1;i<=n;++i)
            printf("%d%c",ans[i],i==n?'\n':' ');
    }
    return 0;
}

看到的一份正确的代码,有时间研究一下

#include <bits/stdc++.h>
#define F first
#define S second
#define pb push_back
#define mk make_pair
#define ll long long
#define dbg(x) printf(#x "=%d\n",x);
#define fastio ios_base::sync_with_stdio(0); cin.tie();
using namespace std;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef vector<int> vi;
typedef vector< pii > vii;
typedef vector<ll> vl;
typedef vector< pll > vll;
const ll MOD=1e9+7;
const ll N=100010;

int n;
vi g[N];
int s,t,mxd;
int d[N],par[N];
bool mark[N];
queue<int> q;

void bfs(){
	fill_n(d,n+1,0);
	fill_n(par,n+1,0);
	par[s]=s;
	q.push(s);
	int i,u;
	while(!q.empty()){
		u=q.front(); q.pop();
		for(i=0;i<g[u].size();++i){
			if(!par[g[u][i]]){
				d[g[u][i]]=d[u]+1;
				par[g[u][i]]=u;
				q.push(g[u][i]);
			}
		}
	}
	t=u;
	mxd=d[t];
}

int dp1[3][N],dp2[3][N];

void dfs(int v,int p,int dp[3][N]){
	vi temp;
	dp[0][v]=dp[1][v]=dp[2][v]=0;
	for(int i=0;i<g[v].size();++i){
		if(g[v][i]!=p){
			dfs(g[v][i],v,dp);
			dp[2][v]=max(dp[2][v],dp[1][g[v][i]]);
			temp.pb(dp[0][g[v][i]]);
		}
	}
	dp[1][v]=dp[2][v];
	sort(temp.begin(),temp.end(),greater<int>());
	if(temp.size()>0){
		dp[0][v]=1+temp[0];
		dp[1][v]=max(dp[1][v],dp[0][v]);
	}
	if(temp.size()>1){
		dp[1][v]=max(dp[1][v],temp[0]+temp[1]+2);
	}
}

int main(){
	int test;
	int i,j,k;
	scanf("%d",&test);
	while(test--){
		scanf("%d",&n);
		for(i=1;i<=n;++i){g[i].clear();
		}
		for(i=1;i<n;++i){
			scanf("%d%d",&j,&k);
			g[j].pb(k);
			g[k].pb(j);
		}
		s=1;
		bfs();
		s=t;
		bfs();
		fill_n(mark,n+1,0);
		for(i=t;par[i]!=i;i=par[i]){
			mark[i]=1;
		}
		mark[s]=1;
		dfs(s,s,dp1);
		dfs(t,t,dp2);
		for(i=1;i<=n;++i){
			if(!mark[i]){
				printf("%d ",mxd);
				// printf("check\n");
				// dbg(i);
			}else{
				printf("%d ",max(dp1[2][i],dp2[2][i]));
			}
		}
		printf("\n");
	}
	return 0;
}

Maximum Tree Path

链接:https://www.codechef.com/problems/MXPATH

F. Tree Destruction CF911F

链接:https://codeforces.com/contest/911/problem/F
题意:给定一颗树,让你选择两个叶节点,累加它们的距离,然后删去其中一个点,连续操作n -1 次,问能够获得的最大累加距离是多少,输出选择和删除的方案。a、b、c,a和b表示选择的点,c表示a和b中删除的点
思路:根据求直径的思路,可以知道,与一个点在树上的最远距离,就是树上直径的两个端点中的一个。因此先删去直径外叶节点,然后删直径上的点。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+10,maxm=500+10;

int n;
vector<int> e[maxn],road;
vector<pair<int,int> > ans;
int d1,d2,depth[maxn],fa[maxn];
int diameter[maxn],dis[maxn][2];
ll sum;
int getdiameter(int s)
{
    int d=0;
    depth[s]=0;
    queue<int> q;
    q.push(s);
    fa[s]=-1;
    while(!q.empty())
    {
        int u=q.front();q.pop();
        d=u;
        for(auto v: e[u])
        {
            if(v==fa[u]) continue;
            fa[v]=u;
            depth[v]=depth[u]+1;
            q.push(v);
        }
    }
    return d;
}
void getroad()
{
    int d=d2;
    while(d!=-1)
    {
        road.push_back(d);
        diameter[d]=1,d=fa[d];
    }
}
void getdis(int u,int fa=-1,int t=0)
{
    for(auto v: e[u])
    {
        if(v==fa) continue;
        dis[v][t]=dis[u][t]+1;
        getdis(v,u,t);
    }
}
void getans(int u,int fa=-1)
{
    for(auto v: e[u])
    {
        if(v==fa) continue;
        if(diameter[v]) continue;
        getans(v,u);
        sum+=max(dis[v][0],dis[v][1]);
        if(dis[v][0]>dis[v][1]) ans.push_back({d1,v});
        else  ans.push_back({d2,v});
    }
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n-1;++i)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        e[u].push_back(v);
        e[v].push_back(u);
    }
    d1=getdiameter(1);
    d2=getdiameter(d1);
    getroad();
    getdis(d1,-1,0);
    getdis(d2,-1,1);

    for(int i=1;i<=n;++i)
        if(diameter[i]) getans(i);
    ll k=(int)road.size()-1;
    sum+=(1+k)*k/2;
    for(int i=0;i<k;++i)
        ans.push_back({d1,road[i]});
    printf("%lld\n",sum);
    for(auto x : ans)
    {
        cout<<x.first<<" "<<x.second<<" "<<x.second<<"\n";
    }
    return 0;
}

Lightning Routing I (2019上海网络赛)

链接:https://nanti.jisuanke.com/t/41398

B. Dynamic Diameter CEOI 2019 day 1 online mirror (unrated, IOI format)

链接:https://codeforces.com/contest/1192/problem/B

参考链接:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值