1. 程式人生 > >CF 1098 C. Construct a tree

CF 1098 C. Construct a tree

opened isdigit char 結點 貪心 bool pen 同時 sum

題目大意:

對於一棵樹,我們定義“分支系數”為子節點最多的節點的子結點個數。現給出結點個數 $n$ 、所有節點的子樹大小之和 $s$ ,同時規定符合條件的樹的根節點必須是 $1$ 。請你判斷是否存在一棵符合條件的樹。如果存在,請你輸出“分支系數”最小的符合條件的樹。輸出方式是,你需要分別輸出編號為 $2$ 的節點~編號為 $n$ 的節點的父節點。

思路:
考慮如果沒有分支系數最小的限制,那麽一條鏈的時候答案為 $i*(i+1)/2$ ,每次把最底層的葉子向上移動一層,答案就減 $1$ ,可以構造出一組解,但是答案不一定優。

顯然,分支系數越大節點的子樹大小之和上界越小。考慮二分出一個答案可行答案,之後在二分出的答案的基礎上把節點貪心的往上層移動,可以構造出一組合法的解。

以下代碼:

技術分享圖片
#include<bits/stdc++.h>
#define il inline
#define LL long long
#define _(d) while(d(isdigit(ch=getchar())))
using namespace std;
const int N=1e5+5;
vector<int> v[N];
int n,fa[N],sz[N];LL s,sum,res;
il int read(){
   int x,f=1;char ch;
   _(!)ch==-?f=-1:f;x=ch^48;
   _()x
=(x<<1)+(x<<3)+(ch^48); return f*x; } il bool pd(int x){ res=0;int t=n;LL now=1;int l=1; while(t){ if(t>=now){ res+=now*l;t-=now; } else{ res+=t*l;t=0; } now*=x;l++; } return res<=s; }
int main() { n=read();scanf("%I64d",&s); if(s<n*2-1||1ll*n*(n+1)/2<s){puts("No");return 0;} if(1ll*n*(n+1)/2==s){ puts("Yes"); for(int i=1;i<n;i++)printf("%d ",i); return 0; } int l=1,r=n,ret=n; while(l<=r){ int mid=(l+r)>>1; if(pd(mid))r=mid-1,ret=mid; else l=mid+1; } for(int i=1;i<=n;i++)v[i].push_back(i); s=1ll*n*(n+1)/2-s; for(int i=2;i<=n;i++)fa[i]=i-1,sz[i-1]=1; int now=1; for(int i=n;i;i--){ while(v[now].size()==0)now++; if(i-now-1<=s){ s-=(i-now-1); int k=v[now].size()-1; int d=v[now][k];sz[d]++; if(sz[d]==ret)v[now].pop_back(); v[now+1].push_back(i); sz[fa[i]]--;fa[i]=d; } else{ int k=i-s-1; int x=v[k][0]; fa[i]=x;s=0; } if(s==0)break; } //printf("!!!%d ",ret); puts("Yes"); for(int i=2;i<=n;i++)printf("%d ",fa[i]); return 0; }
View Code

CF 1098 C. Construct a tree