【备战NOIP2012图论专项模拟试题】砍树

题目描述:

给出一个树形图(“tree-shaped” network),有N(1 <= N <= 10,000)个顶点。如果删除树上某一个顶点,整棵树就会分割成若干个部分。显然,每个部分内部仍保持连通性。
现在问:删除哪个点,使得分割开的每个连通子图中点的数量不超过N/2。如果有很多这样的点,就按升序输出。
例如,如图所示的树形图,砍掉顶点3或者顶点8,分割开的各部件。
这里写图片描述

输入:

第1行:1个整数N,表示顶点数。顶点编号1~N
第2..N行:每行2个整数X和Y,表示顶点X与Y之间有一条边
10
1 2
2 3
3 4
4 5
6 7
7 8
8 9
9 10
3 8

输出:

若干行,每行1个整数,表示一个符合条件的顶点的编号。如果没有顶点符合条件,则仅在第1行上输出”NONE”
3
8


分析:

看到题目以后很容易就能想出枚举根节点,dfs每一个子树,找出最大的节点数。时间复杂度 o(n2) ,会超时。很明显我们有多余的计算,也就是在dfs时重复走到的地方,那怎么把这一部分去掉呢?
我们可以设sum[i]表示节点i的子树的节点数的和,f[i]表示节点i最大子树的节点数,如图:
这里写图片描述


这里写图片描述

那么,以节点i为根节点的时候,它的最大子树子节点数为max(n-sum[i]-1,f[i])。这样我们可以在一次dfs中就找到答案,时间复杂度 o(n)


CODE:

#include<cstdio>
#include<cstring>
using namespace std;
int n,a[30001][2],sum[30001],max1[30001],g[30001];
bool bz[30001];
int max(int a,int b)
{
    if (a>b) return(a);else return(b);
}
void qsort(int l, int r)  
{  
    int i=l;  
    int j=r;  
    int x;
    int t;
    x=a[(l+r)/2][0];
    while (i<=j)  
    {  
        while(a[i][0]<x) i++;  

        while(a[j][0]>x) j--;  
        if(i<=j)
        {   
            t=a[j][0];
            a[j][0]=a[i][0];  
            a[i][0]=t;
            t=a[j][1];
            a[j][1]=a[i][1];  
            a[i][1]=t;
            j--;
            i++;
        }  
    }    
    if (l<j) qsort(l,j);  
    if (r>i) qsort(i,r);  
}
int dfs(int x)
{
    int zl=g[x];
    while (x==a[zl][0])
    {
        if  (bz[a[zl][1]]==false)
        {
            bz[a[zl][1]]=true;
            int z=dfs(a[zl][1]);
            max1[x]=max(max1[x],z);
            sum[x]+=z;

        }
        zl++;
    }
    return(sum[x]+1);
}
int main()
{
    int i,j,k;
    scanf("%d",&n);
    for (i=1;i<=n-1;i++)
    {
        scanf("%d%d",&a[i][0],&a[i][1]);
        a[i+n-1][1]=a[i][0];
        a[i+n-1][0]=a[i][1];
    }
    qsort(1,(n-1)*2);
    int zl=1;
    for (i=2;i<=(n-1)*2;i++)
    {
        if (a[zl][0]!=a[i][0])
        {
            g[a[zl][0]]=zl;
            zl=i;
        }
    }
    g[a[zl][0]]=zl;
    memset(bz,false,sizeof(bz));
    bz[1]=true;
    dfs(1);

    for (i=1;i<=n;i++)
    {
        if (max(n-sum[i]-1,max1[i])<=n/2)
        {
            printf("%d\n",i);
        }
    }
    return 0; 
}

仅供参考,不要copy标,@标哥

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值