Examples

CF1396E Distance Matching 解题报告

CF1396E Distance Matching解题报告:

更好的阅读体验

题意

给定一个 \(n\) 个点的树,求是否能让树上的点两两匹配使得匹配的点距离和为 \(k\)\(n\) 为偶数)。

\(1\leqslant n\leqslant 10^5\)

分析

和最近校内考试的一道题很像。

首先,根据经典结论,合法的 \(k\) 的下界为 \(mn=\sum_{i=1}^n(\text{size}_x\bmod 2)\),上界为 \(mx=\sum_{i=1}^n\min\{\text{size}_x,n-\text{size}_x\}\)

具体证明可以考虑取出一个点对,然后再分治构造。

大胆猜测结论,\(k\) 合法当且仅当 \(mn\leqslant k\leqslant mx\)\(mx\equiv k\pmod 2\)

证明实际上就是构造过程:

我们取重心为根,那么容易发现 \(\text{size}_x\leqslant n-\text{size}_x\) 恒成立,也就是说我们取出所有经过根的路径即可以让答案取到最小值。

由于 \(\sum_{x,y} \text{dis}(x,y)=\sum_{x,y} dep_x+dep_y-2dep_{\text{lca}(x,y)}\)。由于前面是常数,所以我们只要让一对匹配的点对 \(x,y\)\(\text{lca}\) 深度增加一就可以让答案增加二。

接下来是构造过程:

  • 如果 \(k=mx\),那么我们只需要让答案取到最大,这是经典问题,我们求出树的 dfs 序并隔 \(\frac{n}{2}\) 匹配即可。
  • 如果 \(k<mx\),那么我们取出根节点的儿子中子树大小最大的子树 \(x\),容易发现其大小一定不小于 \(2\),,我们取出其中子树大小大于 \(1\) 且最深的节点 \(y\)
    • 如果 \(2\text{dep}_y\leqslant mx-k\),那么我们可以选择两个 \(\text{lca}\)\(y\) 的点进行匹配并删除即可,此时 \(k\) 会增加 \(2\text{dep}_{y}\),但并不会超过 \(mx\),我们回到第一步即可。
    • 如果 \(2\text{dep}_y>mx-k\),那么由于 \(k\equiv mx\pmod 2\) 且深度是连续的,所以一定存在一个 \(\text{dep}_z=\frac{mx-k}{2}\) 的节点 \(z\),我们取出两个 \(\text{lca}\)\(z\) 的节点即可,构造结束。

这里有一个小细节:为什么可以取出一对 \(\text{lca}=y\) 的节点删除。我们发现当 \(y\) 的儿子数量大于 \(1\) 时可以任选两个儿子,儿子数量为 \(1\) 时可以直接选择 \(y\) 和那个儿子。(对 \(z\) 的讨论同理,因为 \(z\)\(y\) 的非负级祖先)

(感觉上面说了一堆废话)

由于第二种情况(\(k<mx\)\(2\text{dep}_y\leqslant mx-k\))不会让重心移动,所以我们并不需要重新寻找重心,于是我们用 \(\text{set}\) 模拟上述过程即可。

时间复杂度 \(O(n\log n)\)

代码

代码很好写。

#include<stdio.h>
#include<set>
#include<vector>
using namespace std;
const int maxn=100005;
int n,cen,mnsz;
int sz[maxn],bel[maxn],used[maxn],dep[maxn],out[maxn],fa[maxn];
long long k,mn,mx,rst;
vector<int>t,dfn,v[maxn];
set< pair<int,int> >sons,s[maxn];
void dfs1(int x,int last){
	int mxsz=0;
	sz[x]=1;
	for(int i=0;i<v[x].size();i++)
		if(v[x][i]!=last)
			dfs1(v[x][i],x),sz[x]+=sz[v[x][i]],mxsz=max(mxsz,sz[v[x][i]]);
	mxsz=max(mxsz,n-sz[x]);
	if(mnsz>mxsz)
		mnsz=mxsz,cen=x;
	if(last)
		mn+=sz[x]%2,mx+=min(sz[x],n-sz[x]);
}
void dfs2(int x,int last,int b){
	sz[x]=1,dep[x]=dep[last]+1,fa[x]=last,bel[x]=b;
	for(int i=0;i<v[x].size();i++)
		if(v[x][i]!=last)
			out[x]++,dfs2(v[x][i],x,b),sz[x]+=sz[v[x][i]];
	if(sz[x]&&out[x]>0)
		s[b].insert(make_pair(dep[x],x));
}
void dfs3(int x,int last){
	if(used[x]==0)
		dfn.push_back(x);
	for(int i=0;i<v[x].size();i++)
		if(v[x][i]!=last)
			dfs3(v[x][i],x);
}
void del(int x){
	used[x]=1,out[fa[x]]--;
	if(out[fa[x]]==0)
		s[bel[x]].erase(make_pair(dep[fa[x]],fa[x]));
}
void match(int x){
	t.clear();
	for(int i=0;i<v[x].size();i++)
		if(v[x][i]!=fa[x]&&used[v[x][i]]==0)
			t.push_back(v[x][i]);
	if(used[x]==0)
		t.push_back(x);
	printf("%d %d\n",t[0],t[1]),del(t[0]),del(t[1]);
}
int main(){
	scanf("%d%lld",&n,&k);
	for(int i=1,x,y;i<n;i++)
		scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
	mnsz=n+1,dfs1(1,0);
	if(mn>k||mx<k||(mx-k)&1){
		puts("NO");
		return 0;
	}
	puts("YES");
	for(int i=0;i<v[cen].size();i++){
		dfs2(v[cen][i],cen,v[cen][i]);
		if(sz[v[cen][i]]>1)
			sons.insert(make_pair(sz[v[cen][i]],v[cen][i]));
	}
	rst=mx-k;
	while(rst){
		int x=sons.rbegin()->second,y=s[x].rbegin()->second;
		sons.erase(make_pair(sz[x],x));
		if(2*dep[y]>rst){
			y=s[x].lower_bound(make_pair((int)(rst/2),0))->second,rst-=2*dep[y],match(y);
			break;
		}
		rst-=2*dep[y],match(y),sz[x]-=2;
		if(sz[x]>1)
			sons.insert(make_pair(sz[x],x));
	}
	dfs3(cen,0);
	for(int i=0;i<dfn.size()/2;i++)
		printf("%d %d\n",dfn[i],dfn[dfn.size()/2+i]);
	return 0;
}
posted @ 2021-10-08 20:26  xiaoziyao  阅读(312)  评论(0)    收藏  举报