[NOIP2018]赛道修建
闲来无事,特意回味一下去年担当NOIPD1T3(防AK却没能防住)之大任的经典好题。
首先看到什么“最短赛道的长度尽可能大”,就知道离不开二分。于是我们想到了一种思路:二分+树上贪心。
二分判定很简单,就是对于二分出来的答案mid,检查树上是否有大于m条不相交的,权值和大于mid的链。而如何凑出尽可能多的权值和大于mid的链并使得它们互不相交,成为了这道题的关键之处。
树上贪心。
贪心策略不是很容易设计,下面给出一种:据题意可知,赛道的构建方式有且只有两种,1)一条链直接作为赛道(如下图中类似于红色的构建方式),2)两条半链(半链的理解对于这个题十分重要,请务必好好理解!)拼成一个赛道(因为不能走回头路因此最多只能两条拼)。我们对于以i为根的子树,先尽可能多的拼出赛道,同时在剩下拼不成整条赛道的情况下,选尽量长的传给父节点,作为父节点半链的候选答案。
根据题意及描述以及样例,易得这是一棵树,我们可以把1号节点作为根,从它开始进行搜索,对于每一个节点i,按照上面的贪心策略进行处理。
同时我们又会遇到一个问题:如何处理半链的拼接呢?显然,当子节点的最优长度加上本条边的长度>=Mid时,直接拼就可以了,如果不能的话,就把它放到multiset中进行维护,二分寻找最短的与当前长度的半链可以拼成整链的半链,如果找不到就打擂台取最大值,把最大的传上去即可。之后我们就可以开心地玩STL了~~~STL是很方便的,如果不会的同学应该尽快学习,尤其对于set,map,vector等写起来难但却非常实用的数据结构,STL简直是不二之选~!!!吹爆STL!!!
下面给出两种不同数据结构的参考代码:
vector(常数贼大,洛谷只能得85分)
1 // luogu-judger-enable-o2 2 #include<iostream> 3 #include<algorithm> 4 #include<cstdio> 5 #include<vector> 6 #define N 1000005 7 using namespace std; 8 int read() 9 { 10 int x=0,f=1;char ch=getchar(); 11 while(ch<'0'||ch>'9'){if(ch=='-')f=0;ch=getchar();} 12 while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();} 13 if(f)return x;return -x; 14 } 15 int n,m,x,y,z,v[N],w[N],head[N],nxt[N],cnt,l=1,r=1e9,mid,sum; 16 vector<int>e[N]; 17 void add(int a,int b,int c) 18 { 19 v[++cnt]=b; 20 w[cnt]=c; 21 nxt[cnt]=head[a]; 22 head[a]=cnt; 23 } 24 vector<int>::iterator it; 25 int dfs(int x,int fa) 26 { 27 e[x].clear(); 28 for(int i=head[x];i;i=nxt[i]) 29 { 30 int y=v[i]; 31 if(y==fa)continue; 32 int val=w[i]+dfs(y,x); 33 if(val>=mid)sum++; 34 else e[x].push_back(val); 35 } 36 sort(e[x].begin(),e[x].end()); 37 int r=0; 38 while(!e[x].empty()) 39 { 40 if(e[x].size()==1)return max(r,*e[x].begin()); 41 it=lower_bound(e[x].begin(),e[x].end(),mid-*e[x].begin()); 42 if(it==e[x].begin())it++; 43 if(it==e[x].end()){r=max(r,*e[x].begin());e[x].erase(e[x].begin());} 44 else{sum++;e[x].erase(it);e[x].erase(e[x].begin());} 45 } 46 return r; 47 } 48 bool check() 49 { 50 sum=0;dfs(1,0); 51 //cout<<mid<<" "<<sum<<endl; 52 return sum>=m; 53 } 54 int main() 55 { 56 n=read();m=read(); 57 for(int i=1;i<n;i++) 58 { 59 x=read();y=read();z=read(); 60 add(x,y,z);add(y,x,z); 61 } 62 while(l<=r) 63 { 64 mid=(l+r)>>1; 65 if(check())l=mid+1; 66 else r=mid-1; 67 } 68 cout<<l-1<<endl; 69 return 0; 70 }
multiset(可AC
1 // luogu-judger-enable-o2 2 // luogu-judger-enable-o2 3 #include<iostream> 4 #include<algorithm> 5 #include<cstdio> 6 #include<vector> 7 #include<set> 8 #define N 1000005 9 using namespace std; 10 int read() 11 { 12 int x=0,f=1;char ch=getchar(); 13 while(ch<'0'||ch>'9'){if(ch=='-')f=0;ch=getchar();} 14 while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();} 15 if(f)return x;return -x; 16 } 17 int n,m,x,y,z,v[N],w[N],head[N],nxt[N],cnt,l=1,r=1e9,mid,sum; 18 multiset<int>e[N]; 19 void add(int a,int b,int c) 20 { 21 v[++cnt]=b; 22 w[cnt]=c; 23 nxt[cnt]=head[a]; 24 head[a]=cnt; 25 } 26 multiset<int>::iterator it; 27 int dfs(int x,int fa) 28 { 29 e[x].clear(); 30 for(int i=head[x];i;i=nxt[i]) 31 { 32 int y=v[i]; 33 if(y==fa)continue; 34 int val=w[i]+dfs(y,x); 35 if(val>=mid)sum++; 36 else e[x].insert(val); 37 } 38 //sort(e[x].begin(),e[x].end()); 39 int r=0; 40 while(!e[x].empty()) 41 { 42 if(e[x].size()==1)return max(r,*e[x].begin()); 43 it=e[x].lower_bound(mid-*e[x].begin()); 44 if(it==e[x].begin()&&e[x].count(*it)==1)it++; 45 if(it==e[x].end()){r=max(r,*e[x].begin());e[x].erase(e[x].find(*e[x].begin()));} 46 else{sum++;e[x].erase(e[x].find(*it));e[x].erase(e[x].find(*e[x].begin()));} 47 } 48 return r; 49 } 50 bool check() 51 { 52 sum=0;dfs(1,0); 53 //cout<<mid<<" "<<sum<<endl; 54 return sum>=m; 55 } 56 int main() 57 { 58 n=read();m=read(); 59 for(int i=1;i<n;i++) 60 { 61 x=read();y=read();z=read(); 62 add(x,y,z);add(y,x,z); 63 } 64 while(l<=r) 65 { 66 mid=(l+r)>>1; 67 if(check())l=mid+1; 68 else r=mid-1; 69 } 70 cout<<l-1<<endl; 71 return 0; 72 }
)