题目大意
给出一棵无根树,每个节点有一个权值,现在要让dfs序的前k个结点的最小值最大,求出这个值。
分析
首先可以对这个值v进行二分然后01分数规划
现在问题转化为求出一个dfs序,使得dfs序中的至少有k个1,这一步可以用树形dp来做。
用dp[u]表示从节点u开始在子树中进行dfs最多可以经过多少个为1的结点,显然,若某一个子树中节点全为1,那么这个可以加到dp[u]中,此外还可以在不全为1的子树中挑选一个加到dp[u]上。
现在考虑我们要求的答案,对于当前的结点u,那么答案为两棵不完全子树的dp值加上所有的完全子树(父亲往上延伸的部分也认为是子树),现在考虑父亲往上延伸的部分
若父亲往上延伸的部分是一个完全子树,那么只需要加上这个值即可
问题的关键是如果父亲往上延伸的部分是一棵不完全子树该怎么做,可以这样想,从当前结点直到祖先节点中,肯定有一个结点从父亲往上延伸部分要么是一棵完全子树,要么不能往上延伸,所以对于每一个子树,我们只需要处理父亲往上延伸为一棵完整子树的情况即可,因为不完全子树这种情况肯定会在祖先节点中被处理于是我们用所有数中的最小值作为根,于是根在除了答案为最小值的情况下均为0,这样就可以避免父亲往上是一棵完整子树的情况了
代码
#include<iostream> #include<cstdio> #include<cstring> #include<string> #include<algorithm> #include<cctype> #include<cmath> #include<cstdlib> #include<queue> #include<ctime> #include<vector> #include<set> #include<map> #include<stack> using namespace std; int n,k,a[200100],now[200100],siz[200100],Ans,all[200100],mid,pl; vector<int>v[200100]; inline void work(int x,int fa){ siz[x]=all[x]=1; int maxn=0,sen=0; for(int i=0;i<v[x].size();i++) if(v[x][i]!=fa){ work(v[x][i],x); all[x]+=all[v[x][i]]; if(siz[v[x][i]]==all[v[x][i]])siz[x]+=siz[v[x][i]]; else { if(siz[v[x][i]]>maxn)swap(maxn,siz[v[x][i]]); if(siz[v[x][i]]>sen)swap(sen,siz[v[x][i]]); } } siz[x]+=maxn; if(a[x]<mid)siz[x]=0; Ans=max(Ans,siz[x]+sen); } inline bool ck(){ Ans=0; work(pl,0); if(Ans>=k)return 1; else return 0; } int main(){ int i,minn=1e9+7; scanf("%d%d",&n,&k); for(i=1;i<=n;i++){ scanf("%d",&a[i]); if(a[i]<minn)minn=a[i],pl=i; } for(i=1;i<n;i++){ int x,y; scanf("%d%d",&x,&y); v[x].push_back(y); v[y].push_back(x); } int le=1,ri=1e6+9; while(ri-le>1){ mid=(le+ri)>>1; if(ck())le=mid; else ri=mid; } cout<<le; return 0; }