Tree NOIp2013-Training Series #4

来源:互联网 发布:通用网址和中文域名 编辑:程序博客网 时间:2024/06/08 09:28

Description

给出N 个点的树和K,问能否把树划分成N/K 个连通块,且每个连通块的点数都是K。

Input

第1 行,1 个整数T,表示数据组数。接下来T 组数据,对于每组数据: 
第1 行,2 个整数N;K。 
接下来(N -1) 行,每行2 个整数Ai,Bi,表示边(Ai,Bi)。点用1,2,...,N 编号。

Output

对于每组数据,输出YES或NO。

Sample Input

24 21 22 33 44 21 21 31 4

Sample Output

YESNO

Hint

对于60% 的数据,1<=N,K<=10^3; 
对于100% 的数据,1<=T<=10,1<=N,K<=10^5。


【分析】

    首先想清楚,如果答案是YES,那么这棵树的拆分方法是唯一的!
    这个结论要严格的证明我也不会证,但是仔细想想好像还是可以理解。
        于是我们任选一个点为根,开始dfs,dfs的核心目的是找出子树的节点数count[i]
        当然,单纯的找count是不行的,我们要增加如下操作
            1.dfs之前,ans=true;
            2.在以i为根的子树dfs的时候,如果i的某个儿子节点j的count[j]==K,那么j子树是要独立成为以个K点集合,在计算count[i]的时候就不要把count[j]加进去,相当于删除j这个子树;
            3.如果i的某个儿子节点j的count[j]<K,说明这棵子树节点数不够,必须把节点i以及更多节点加入这个集合,所以必须count[i]+=count[j],相当于合并;
            4.如果count[j]>K,那么这已经超出我们的要求了,所以直接{ans=false;return;}
    一开始可能觉得这个算法有点贪心的意味在里面,可能是错的,但是经过草稿纸分析,代码亲测,这个算法是对的!
    只需要注意各种memset
    然后就可以去AC了!


【代码】

#include<cstdio>#include<cstdlib>#include<cstring>#include<cmath>#include<iostream>#include<algorithm>#include<queue>using namespace std;const int MAXN=100005;int N,K;int y[MAXN<<1],next[MAXN<<1],last[MAXN],cnt[MAXN],tot;bool f[MAXN],ans;void _in(int &x){char t=getchar();while(t<'0'||'9'<t) t=getchar();for(x=0;'0'<=t&&t<='9';x=x*10+t-'0',t=getchar());}void _init(){memset(f,0,sizeof(f));memset(y,0,sizeof(y));memset(next,0,sizeof(next));memset(last,0,sizeof(last));memset(cnt,0,sizeof(cnt));_in(N);_in(K);tot=0;int a,b;for(int i=1;i<N;i++){_in(a);_in(b);tot++;y[tot]=b;next[tot]=last[a];last[a]=tot;tot++;y[tot]=a;next[tot]=last[b];last[b]=tot;}ans=true;}void _DFS(int x){f[x]=true;cnt[x]=1;for(int j=last[x];j;j=next[j])    if(!f[y[j]]){_DFS(y[j]);if(ans==false) return;if(cnt[y[j]]<K){cnt[x]+=cnt[y[j]];if(cnt[x]>K){ans=false;return;}}else if(cnt[y[j]]>K){ans=false;return;}}}void _solve(){    _DFS(1);printf(ans?"YES\n":"NO\n");}int main(){int T;scanf("%d",&T);while(T--){    _init();    _solve();}return 0;}


原创粉丝点击