CF1396E Distance Matching

一、题目

点此看题

二、解法

技巧性极强的构造题,惜吾构造而不终也,思路大体有了,但还差点火候

首先考虑合法的必要条件,我们先考察边权的最大值和最小值来得到大体的范围。我们考虑每条边的贡献,边 \((u,v)\) 断开后形成的子树大小是 \(siz[u],siz[v]\),可以得到上下界分别是:

\[siz[u]\%2\leq w\leq\min(siz[u],siz[v]) \]

为了去掉 \(\min\),我们取中心为根,那么现在贡献上界是 \(\sum_{u\not=rt}siz[u]\),下界是 \(\sum_{u}siz[u]\%2\)

继续考虑必要条件,因为 \(dis(x,y)=dep[x]+dep[y]-2dep[lca]\),更换点的匹配只会引起 \(lca\) 这一项的变化,所以无论如何边权和的奇偶性不变

所以必要条件是:\(mi\leq k\leq mx,k=mx\bmod 2\),现在考虑给出它的充分性证明,直接上构造:

考虑从边权和为 \(mx\) 的方案调整到为 \(k\) 的方案,一开始可以把 \(\tt dfn\) 序为 \(i\) 的点和 \(\tt dfn\) 序为 \(i+n/2\) 的点配对,这样是能构造到 \(mx\) 的(下文称之为初方案),而且所有路径都跨过重心,我们在构造时考虑维护根为重心的这个性质,所以我们选取点数最大的子树来操作,设 \(y\) 表示最深的非叶节点:

  • 如果 \(2dep[y]<mx-k\),我们直接拿 \(y\) 配对(优先儿子,可以自身),那么配对后相较于初方案的边权和会减少 \(2dep[y]\),配对后删除这两个点,树的形态不变,我们在新树上构造出新的初方案。
  • 如果 \(2dep[y]\geq mx-k\),因为 \(dep\) 连续这一特点,我们找到 \(dep[z]=\frac{mx-k}{2}\),然后类似于上述方案配对即可,边权和会减少 \(2dep[z]\),这说明我们找到了答案。

如果还有未匹配的点就按初方案来构造即可,用 \(\tt set\) 轻松维护,时间复杂度 \(O(n\log n)\)

三、总结

构造出某一特殊取值的题可以考虑上下界,然后从某一边界开始调整。

树上路径和问题可以使用贡献法,考虑边的贡献。

如果树上遇到 \(\min(..siz..)\) 之类的限制,可以通过取重心为根的方式来去除。

#include <cstdio>
#include <cassert>
#include <iostream>
#include <set>
using namespace std;
const int M = 100005;
#define int long long
#define pii pair<int,int>
#define mp make_pair
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,tot,rt,f[M],cur[M],cl[M],siz[M],mx[M],fa[M],dep[M];
set<pii> s,b[M];int m,k,rs,mi,dfn[M],use[M],out[M];
struct edge
{
	int v,next;
}e[2*M];
void dfs1(int u,int fa)
{
	siz[u]=1;
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==fa) continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		mx[u]=max(mx[u],siz[v]);
	}
	mx[u]=max(mx[u],n-siz[u]);
	if(mx[u]<mx[rt] || !rt) rt=u;
}
void dfs2(int u,int p,int rt)
{
	siz[u]=1;cl[u]=rt;
	fa[u]=p;dep[u]=dep[p]+1;
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==p) continue;
		out[u]++;
		dfs2(v,u,rt);
		siz[u]+=siz[v];
	}
	mi+=siz[u]%2;rs+=siz[u];
	if(out[u]) b[rt].insert(mp(dep[u],u));
}
void dfs3(int u)
{
	if(!use[u]) dfn[++m]=u;
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==fa[u]) continue;
		dfs3(v);
	}
}
void del(int u)
{
	int x=fa[u];
	out[x]--;use[u]=1;
	if(out[x]==0) b[cl[x]].erase(mp(dep[x],x));
}
void match(int u)
{
	int t=0,p[5]={};
	for(int &i=cur[u];i && t<2;i=e[i].next)
	{
		int v=e[i].v;
		if(!use[v] && v!=fa[u]) p[++t]=v;
	}
	if(!use[u]) p[++t]=u;
	assert(t>=2);
	printf("%lld %lld\n",p[1],p[2]);
	del(p[1]);del(p[2]);
}
signed main()
{
	n=read();k=read();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		e[++tot]=edge{v,f[u]},f[u]=tot;
		e[++tot]=edge{u,f[v]},f[v]=tot;
	}
	for(int i=1;i<=n;i++) cur[i]=f[i];
	dfs1(1,0);//find root
	for(int i=f[rt];i;i=e[i].next)
	{
		int v=e[i].v;
		dfs2(v,rt,v);//get the dep
		if(siz[v]>1) s.insert(mp(siz[v],v));
	}
	if(mi>k || rs<k || rs%2!=k%2)
	{
		puts("NO");
		return 0;
	}
	puts("YES");
	rs=rs-k;
	while(rs)
	{
		int x=s.rbegin()->second,y=b[x].rbegin()->second;
		s.erase(mp(siz[x],x));
		if(2*dep[y]>=rs)//we can get the answer
		{
			y=b[x].lower_bound(mp(rs/2,0))->second;
			match(y);break;
		}
		rs-=2*dep[y];siz[x]-=2;
		match(y);
		if(siz[x]>1) s.insert(mp(siz[x],x));
	}
	dfs3(rt);
	assert(m%2==0);
	for(int i=1;i<=m/2;i++)
		printf("%lld %lld\n",dfn[i],dfn[i+m/2]);
}
posted @ 2021-10-22 21:40  C202044zxy  阅读(147)  评论(0编辑  收藏  举报