Loading

题解 【P5021 [NOIP2018 提高组] 赛道修建】

\(\LARGE\texttt{P5021}\)

做法:贪心/二分/\(\texttt{DFS}\)(树形 \(\texttt{DP}\))/并查集

前言

清一色的 \(\texttt{multiset}\) ,为什么啊?

这的确是一道好题,做法很多,对于这里贪心的维护难道非要经行删除操作?(还有为什么有人说显然要 \(\texttt{multiset}\) ?)像我这样的蒟蒻不会 \(\texttt{multiset}\) 怎么办?

提供一种不用 \(\texttt{multiset}\) 的做法(码量小)。

题意

给你一颗带权树,要你选 \(k\) 条没有重边的链,使得这些链中权值最小值最大。

这里链的权值定义为链上所有边权之和。

思路

好像做过一道类似的题

\(\texttt{undate}\):确实为\(\texttt{P6147}\)的加强版。

看到最小值最大,显然二分答案,且有十分明显的单调性,然后 \(check\) 一下就好了。

\(check\) 里面是一个经典的树形 \(\texttt{DP}\),考虑从叶子节点往上处理,因为对于每个节点向上到父亲的边只有一条,所以对于每个节点记录从这个节点一直往下最深并没有选进链的链(以下记为 \(s[i]\) ,如何得到这个值请往下看)。

对于每一个节点,我们要进行两种操作。

  • 若以这个节点为链的端点,有大于目标值的链,直接对答案贡献 \(+1\)

  • 若以这个节点为某一条链,两个端点的 \(\texttt{LCA}\) ,则要做一个拼接操作,将两个儿子的 \(s[i]+dis(fa,son_i)\) 值合并,但这两个 \(s\) 值相加必须要大于目标值。

    剩下的儿子节点中最大的 \(s[i]+dis(fa,son_i)\) 作为 \(s[fa]\)

可以证明,先要优先最大化合并操作的次数,因为若要优先最大化 \(s[i]\) ,则可能这第 \(i\) 个节点的合并个数会变小,但 \(s[i]\) 增大最多对i的父节点的贡献 \(+1\) 。(贪心*1)

但是在优先最大化每个节点合并的次数,还要次优先最大化 \(s[i]\) ,原本我有一个错误的贪心:是先排序 \(s[son\_of\_i]\) ,然后不断将最左边的数和最右边的数合并,若不能合并,则将最左边的值删去。

但是这个贪心是错的,参考样例:2 8 9 目标值:10 wrong:s[n]=8 ans:s[n]=9

发现这里疏忽了一点,就是应该选择这个数最小能匹配的数,可以用 \(\texttt{lower\_bound}\) 求出。(贪心*2)

求出那个数之后,要将它删去,这里我用 \(\Large\texttt{并查集}\) 来维护,若第 \(i\) 个数被删去,就将自己的父亲定为 \(i+1\) 。然后取二分到的那个数的祖宗,若这个祖宗是 \(s.size()+1\) (即这个数后面的数都被删完了),就跳过这个数。

时间复杂度 \(O((\log\sum val)\times N\log N\times\alpha(N))\) (远远达不到的,本人不会均摊QwQ

代码

#include <bits/stdc++.h>
using namespace std;

 #define PB push_back
 #define MP make_pair
 const int N = 5e4;
 #define pii pair<int,int>
inline int read()
{
    int s = 0;
    register bool neg = 0;
    register char c = getchar();
    for (; c < '0' || c > '9'; c = getchar())
        neg |= (c == '-');
    for (; c >= '0' && c <= '9'; s = s * 10 + (c ^ 48), c = getchar())
        ;
    s = (neg ? -s : s);
    return s;
}

int a,b,p,res,s[N+10],nxt[N+10],lst[N+10],Fa[N+10];
bool vis[N+10];
vector<pair<int,int> >st[N+10];

inline int f(int n) {
	return Fa[n]==n?n:Fa[n]=f(Fa[n]);
}

inline void dfs(int n,int fa) {
	s[n]=0;
	vector<int> q;
	q.clear();
	for(int i=0;i<st[n].size();i++) {
		int v=st[n][i].first,val=st[n][i].second;
		if(v==fa) continue;
		dfs(v,n);
		if(s[v]+val>=p) res++;
		else q.PB(val+s[v]);
	}
	if(!q.size()) return;
	if(q.size()==1) {
		s[n]=q[0];
		return;
	}
	sort(q.begin(),q.end());
	for(int i=0;i<=(int)q.size();i++) Fa[i]=i,vis[i]=0;
	for(int i=0;i<(int)(q.size()-1);i++) {
		if(vis[i]) continue;
		if(i>=q.size()-1||i==-1) break;
		int t=lower_bound(q.begin()+i+1,q.end(),p-q[i])-q.begin();
		if(t>=q.size()) continue;
		t=f(t);
		if(t>=q.size()) continue;
		if(q[t]+q[i]<p) continue;
		vis[t]=1;
		vis[i]=1;
		Fa[t]=t+1;
		res++;
	}
	for(int i=(int)(q.size()-1);i>=0;i--) if(!vis[i]) {
		s[n]=q[i];
		break;
	}
}

inline bool check(int n) {
	p=n;
	res=0;
	dfs(1,0);
	return res>=b;
}

signed main()
{
//	freopen("in.txt","r",stdin);
	a=read();
	b=read();
	int x,y,z,mx=0;
	for(int i=1;i<a;i++) {
		x=read();
		y=read();
		z=read();
		mx+=z;
		st[x].PB(MP(y,z));
		st[y].PB(MP(x,z));
	}
	int l=1,r=mx,ans=0;
	while(l<=r) {
		int mid=(l+r)>>1;
		if(check(mid)) l=mid+1,ans=mid;
		else r=mid-1;
	}
	printf("%d",ans);
	return 0;
}
posted @ 2020-12-04 10:09  RedreamMer  阅读(161)  评论(0编辑  收藏  举报