ZOJ Monthly, June 2018 - I District Division

Ezio learned a lot from his uncle Mario in Villa Auditore. He also made some contribution to Villa Auditore. One of the contribution is dividing it into many small districts for convenience of management. If one district is too big, person in control of this district would feel tiring to keep everything in order. If one district is too small, there would be too many districts, which costs more to set manager for each district.

There are  rooms numbered from 1 to  in Villa Auditore and  corridors connecting them. Let's consider each room as a node and each corridor connecting two rooms as an edge. By coincidence, Villa Auditore forms a tree.

Ezio wanted the size of each district to be exactly , which means there should be exactly  rooms in one district. Each room in one district should have at least one corridor directly connected to another room in the same district, unless there are only one room in this district (that is to say, the rooms in the same district form a connected component). It's obvious that Villa Auditore should be divided into  districts.

Now Ezio was wondering whether division can be done successfully.

Input

There are multiple test cases. The first line of the input contains an integer  (about 10000), indicating the number of cases. For each test case:

The first line contains two integers  (), indicating the number of rooms in Vally Auditore and the number of rooms in one district.

The following  lines each contains two integers  (), indicating a corrider connecting two rooms  and .

It's guaranteed that:

  •  is a multiple of ;

  • The given graph is a tree;

  • The sum of  in all test cases will not exceed .

Output

For each test case:

  • If the division can be done successfully, output "YES" (without quotes) in the first line. Then output  lines each containing  integers seperated by one space, indicating a valid division plan. If there are multiple valid answers, print any of them.

  • If the division cannot be done successfully, output "NO" (without quotes) in the first line.

Please, DO NOT output extra spaces at the end of each line, or your answer will be considered incorrect!

Sample Input

3
4 2
1 3
3 2
1 4
6 3
1 3
1 4
1 6
2 5
5 1
8 4
1 2
2 3
2 4
1 5
5 6
5 7
5 8

Sample Output

YES
1 4
2 3
NO
YES
4 3 2 1
5 6 7 8

大意是把一棵书划分成成若干个区域,问你有没有可能,如果可能,输出路径。
首先,我们要想到什么时候是没有可能的。我们随便以一个点为根,统计所有点的子树(包括它自己),然后我们可以得到一个size[i],如果size[i]%k=0的个数等于n/k,也就是要划分的个数,那么必然能被划分。
为什么?我们首先有n/k个 sizes[i]%k=0的点,对不对?其中一定有sizes[i]=k的点,而且它必然是sizes[j]%k==0&&sizes[j]>k的子树(想一想为什么),不然它无法构成一棵树(树嘛,n个点,n-1条边,如果你的较小子树不包含在较大的类,你又有n/k个区域,一加点岂不是多了)。
然后我们就能得到yes或no了。
接着是最麻烦的,记录路径。
先从最小的sizes[i]%k==0的子树开始搜,然后标记已经打印的点,然后再搜次大的。因为我们刚才已经说了,如果k=4,那么先搜子树为4的,再搜子树为8的,由于4的已经被搜过而且记录了,再搜8的就不会打印重复。
#include <bits/stdc++.h>
#define maxn 100005
using namespace std;
int head[maxn];
int sizes[maxn];
bool vis[maxn];
int n;
struct edge
{
    int u=0,v=0,next=0;
}edge[maxn*2];
int cnt=0;
int fa[maxn];
void init()
{   int i;
    for(i=1;i<=n;i++)
        {
            head[i]=-1;
        }
    for(i=1;i<=2*n;i++)
        {
            edge[i].u=0;
            edge[i].v=0;
            edge[i].next=0;
        }
        for(i=1;i<=n;i++) vis[i]=false;
        for(i=1;i<=n;i++) sizes[i]=0;
        cnt=0;
}//初始化,如果直接用memset会超时。
void addedge(int u,int v)
{
    edge[cnt].u=u;
    edge[cnt].v=v;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}//邻接表建图
void dfs(int u)
{
    int i,flag=0;
    for(i=head[u];i!=-1;i=edge[i].next)
    {   if(!vis[edge[i].v])
       {   vis[edge[i].v]=true;
           fa[edge[i].v]=u;
           dfs(edge[i].v);
           sizes[u]+=sizes[edge[i].v];
           flag=1;
       }
    }
    if(flag==0) sizes[u]=1;
    else sizes[u]++;
}//第一次dfs,统计每个点有多少个子树
struct node
{
    int val=0;
    int id=0;
}nodes[maxn];//这个结构体用来记录所有sizes[i]%k==0的i,和其对应的子树
bool cmp(node a,node b)
{
    return a.val<b.val;
}
int f=0;
void solve(int num)
{
    int i;
    if(f) printf(" ");
    printf("%d",num);
    f=1;
    for(i=head[num];i!=-1;i=edge[i].next)
    {
        if(edge[i].v!=fa[num])
        {
            if(!vis[edge[i].v])
            {
                vis[edge[i].v]=true;
                solve(edge[i].v);
            }
        }
    }
}//第二次dfs,打印路径
int main()
{

    int t;
    cin>>t;
    memset(head,-1,sizeof(head));
    while(t--)
    {
        init();
        int k,i,u,v;
        scanf("%d%d",&n,&k);
        for(i=1;i<=n-1;i++)
        {
            scanf("%d%d",&u,&v);
            addedge(u,v);
            addedge(v,u);
        }
        vis[1]=true;
        dfs(1);//随便以一个节点为根节点搜就行
        int sum=0;
        for(i=1;i<=n;i++)
        {
            if(sizes[i]%k==0) nodes[sum].id=i,nodes[sum].val=sizes[i],sum++;
        }//这样我就把所有满足要求的点全部压进结构体了
        if(sum<n/k) printf("NO\n");
        else
        {
            printf("YES\n");
            for(i=1;i<=n;i++) vis[i]=false;
            sort(nodes,nodes+sum,cmp);//排序,先从子树小的开始打印
            for(i=0;i<sum;i++)
            {   f=0;
                //printf("%d ",nodes[i].id);
                vis[nodes[i].id]=true;
                solve(nodes[i].id);
                printf("\n");
            }

        }

     }
    return 0;
}

  



posted @ 2018-06-11 14:54  行远山  阅读(339)  评论(0编辑  收藏  举报