首页 > 其他分享 >【CF1396E】Distance Matching(构造)

【CF1396E】Distance Matching(构造)

时间:2022-10-28 20:04:51浏览次数:57  
标签:Distance 匹配 sz int 下界 Seg CF1396E data Matching

题意:

给一棵 \(n\) 个点的树,保证 \(n\) 为偶数,你需要将这 \(n\) 个点两两配对,使得每对点的距离和恰好为 \(k\)。判断无解或输出方案。

\(n\leq 10^5,k\leq n^2\)。

题解:

首先考虑 \(k\) 的上下界,这是一个很经典的问题,可以考虑每条边 \((u,v)\) 的贡献:假设左右两边子树大小分别为 \(sz_u,sz_v\),那么该边贡献下界为 \(sz_u\bmod 2\),上界为 \(\min(sz_u,sz_v)\)。于是 \(k\) 的上下界即为所有边的总和,记为 \([k_L,k_R]\)。

证明上下界是确界可以用调整法:若一条边被至少两条路径经过,那么交换这两组匹配;若一条边 \(sz\) 较小的那棵子树内有一对不出子树的匹配,那么对于 \(sz\) 较大的那棵子树内也一定有这样的匹配,那么交换这两组匹配。调整次数是有限的,因为 \(k\) 都在严格地朝目标方向变化。

通过证明,我们也可以得到另一个观察:当 \(k\) 取到上界时,任意两组匹配对应的路径都有交,可以推出所有匹配对应的路径都有交,然后得到这个交点就是重心。

从另一个角度看,我们把该树看作以重心为根的有根树,那么我们要最大化 \(\sum_{(a,b)\in M}d_a+d_b-d_{lca(a,b)}\),而显然容易做到每组 \(d_{lca(a,b)}\) 都等于 \(0\)。

同时,因为一组匹配可以通过第二段中所述的 “调整法” 变成上下确界的解,而一次调整必使 \(k\) 改变偶数,那么我们得到某个 \(k\) 有解的必要条件是 \(k\equiv k_L\equiv k_R\pmod 2\)。

猜想任意一个满足上述条件的 \(k\) 都存在解。考虑如何构造:一个思路是从上下界开始,利用第二段中所述的 “调整法” 调整到我们要找的解。例如,从上界开始,每次找两条交为 \(1\) 的路径,并把它们交换,使 \(k\) 减少 \(2\)。但发现这并不好做:首先我们要找到一种调整方案,使得每次都能找到两条交为 \(1\) 的路径,直到我们要的 \(k\) 为止(更极端地,直到达到下界为止);其次,调整过程如果不能加速的话,该算法的时间也是不可承受的。

题解是归纳地构造:考虑每次找到 lca 深度最大的两个点,把它们匹配,并把它们从树中删除(删除它们后树肯定仍然连通),直到再匹配就要使得当前的 \(k\) 大于剩下的树的 \(k\) 的上界为止,此时我们再特殊地考虑一下怎么构造。可以证明这种方法其实就是构造下界的一种方法,所以它是正确的。而且注意到,只要我们每次都从当前根的子树大小最大的那个子树内选,树的重心就一直不会变,那么 \(k\) 的上界也好维护。

类似地我们有另一种方法:考虑每次选择两个 lca 为根的叶子,把它们匹配,并把它们从树中删除,直到再匹配就要使当前 \(k\) 小于剩下的树的 \(k\) 的下界为止,此时我们再特殊地考虑一下怎么构造。注意每次选择不能乱选,要保证其中一个叶子是在当前根的子树大小最大的那个子树内,否则这个方法并不能达到上界。

试着写的后面一种方法。具体在最后怎么特殊构造:假设矛盾的两个点分别为 \(a,b\),它们之间路径长度为 \(l\),上面的 \(0,1\) 边(边两侧子树大小的奇偶性)分别有 \(n_0,n_1\) 条。那么将 \(a,b\) 匹配后,会将路径上所有边 \(0,1\) 翻转而其他边不动。因为矛盾,则有 \(k-l<k_L-n_1+n_0\),即 \(2n_0>k-k_L\)。那么我们考虑调整 \(a,b\),使得 \(a,b\) 匹配后,剩下的 \(k\) 恰好是剩下的树的 \(k_L'\)。这意味着对于新的 \(a,b\) 而言,有 \(k-l=k_L-n_1+n_0\),即 \(2n_0=k-k_L\)。于是我们将 \(a,b\) 不断往父亲跳,直到该条件恰好满足为止,然后将它们匹配。然后对于剩下的点跑一遍 dfs 构造下界 \(k_L'\) 的方案。

需要实现到根链异或和到根链求和,写的粗糙了些,用树剖实现,时间复杂度 \(O(n\log^2n)\)。

#include<bits/stdc++.h>

#define N 100010
#define ll long long

using namespace std;

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

ll k;
int n,rt;
int cnt,head[N],to[N<<1],nxt[N<<1];
int idx,d[N],fa[N],size[N],son[N],top[N],id[N],rk[N];
int du[N],srt[N];
bool match[N];

void adde(int u,int v)
{
	to[++cnt]=v;
	nxt[cnt]=head[u];
	head[u]=cnt;
}

void findroot(int u,int fa)
{
	size[u]=1;
	int nmax=0;
	for(int i=head[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v==fa) continue;
		findroot(v,u);
		size[u]+=size[v];
		nmax=max(nmax,size[v]);
	}
	nmax=max(nmax,n-size[u]);
	if((nmax<<1)<=n) rt=u;
}

void dfs(int u)
{
	size[u]=1;
	for(int i=head[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v==fa[u]) continue;
		du[u]++,d[v]=d[u]+1,fa[v]=u;
		dfs(v),size[u]+=size[v];
		if(size[v]>size[son[u]]) son[u]=v;
	}
}

void dfs1(int u,int tp)
{
	top[u]=tp;
	rk[id[u]=++idx]=u;
	if(son[u]) dfs1(son[u],tp);
	for(int i=head[u];i;i=nxt[i])
		if(to[i]!=fa[u]&&to[i]!=son[u])
			dfs1(to[i],to[i]);
}

queue<int> q[N];

void dfs2(int u)
{
	bool leaf=1;
	for(int i=head[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v==fa[u]) continue;
		leaf=0,srt[v]=srt[u],dfs2(v);
	}
	if(leaf) q[srt[u]].push(u);
}

struct data
{
	int n[2];
	data(){}
	data(int a,int b){n[0]=a,n[1]=b;}
	data operator + (const data &a){return data(n[0]+a.n[0],n[1]+a.n[1]);}
	void operator += (const data &a){(*this)=(*this)+a;}
	void swap(){std::swap(n[0],n[1]);}
};

namespace Seg
{
	data s[N<<2];
	bool rev[N<<2];
	void up(int k){s[k]=s[k<<1]+s[k<<1|1];}
	void build(int k,int l,int r)
	{
		if(l==r)
		{
			s[k].n[size[rk[l]]&1]++;
			return;
		}
		int mid=(l+r)>>1;
		build(k<<1,l,mid);
		build(k<<1|1,mid+1,r);
		up(k);
	}
	void downn(int k){s[k].swap(),rev[k]^=1;}
	void down(int k)
	{
		if(rev[k])
		{
			downn(k<<1),downn(k<<1|1);
			rev[k]=0;
		}
	}
	void update(int k,int l,int r,int ql,int qr)
	{
		if(ql<=l&&r<=qr)
		{
			downn(k);
			return;
		}
		down(k);
		int mid=(l+r)>>1;
		if(ql<=mid) update(k<<1,l,mid,ql,qr);
		if(qr>mid) update(k<<1|1,mid+1,r,ql,qr);
		up(k);
	}
	data query(int k,int l,int r,int ql,int qr)
	{
		if(ql<=l&&r<=qr) return s[k];
		down(k);
		int mid=(l+r)>>1;
		if(qr<=mid) return query(k<<1,l,mid,ql,qr);
		if(ql>mid) return query(k<<1|1,mid+1,r,ql,qr);
		return query(k<<1,l,mid,ql,qr)+query(k<<1|1,mid+1,r,ql,qr);
	}
}

data query(int a)
{
	data ans(0,0);
	while(top[a]!=rt)
	{
		ans+=Seg::query(1,1,n,id[top[a]],id[a]);
		a=fa[top[a]];
	}
	if(a!=rt) ans+=Seg::query(1,1,n,id[rt]+1,id[a]);
	return ans;
}

void update(int a)
{
	while(top[a]!=rt)
	{
		Seg::update(1,1,n,id[top[a]],id[a]);
		a=fa[top[a]];
	}
	if(a!=rt) Seg::update(1,1,n,id[rt]+1,id[a]);
}

int dfs3(int u)
{
	vector<int> p;
	if(!match[u]) p.push_back(u);
	for(int i=head[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v==fa[u]) continue;
		int tmp=dfs3(v);
		if(tmp) p.push_back(tmp);
	}
	int sp=p.size();
	for(int i=0;i+1<sp;i+=2)
		printf("%d %d\n",p[i],p[i+1]);
	return (sp&1)?p[sp-1]:0;
}

int main()
{
	scanf("%d%lld",&n,&k);
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		adde(u,v),adde(v,u);
	}
	findroot(1,0);
	dfs(rt);
	ll kl=0,kr=0;
	for(int i=1;i<=n;i++)
		kl+=(size[i]&1),kr+=d[i];
	assert((kl&1)==(kr&1));
	if(k<kl||k>kr||((kl&1)^(k&1)))
	{
		puts("NO");
		return 0;
	}
	puts("YES");
	dfs1(rt,rt);
	Seg::build(1,1,n);
	assert(kl==Seg::s[1].n[1]);
	set<pair<int,int>> s;
	for(int i=head[rt];i;i=nxt[i])
	{
		int v=to[i];
		s.insert(make_pair(size[v],v));
		srt[v]=v,dfs2(v);
	}
	s.insert(make_pair(0,0));
	while(1)
	{
		assert(Seg::s[1].n[1]<=k);
		auto it=s.end();
		--it; int sA=(*it).first,A=(*it).second;
		--it; int sB=(*it).first,B=(*it).second;
		if(!sB)
		{
			assert(sA==1);
			printf("%d %d\n",rt,q[A].front());
			return 0;
		}
		int a=q[A].front(); q[A].pop();
		int b=q[B].front(); q[B].pop();
		auto ps=query(a)+query(b);
		if((ps.n[0]<<1)>k-Seg::s[1].n[1])
		{
			if(!(k-Seg::s[1].n[1]))
			{
				assert(!dfs3(rt));
				return 0;
			}
			int n0=ps.n[0];
			while(a!=rt&&(n0<<1)>k-Seg::s[1].n[1])
			{
				n0-=Seg::query(1,1,n,id[a],id[a]).n[0];
				a=fa[a];
			}
			while(b!=rt&&(n0<<1)>k-Seg::s[1].n[1])
			{
				n0-=Seg::query(1,1,n,id[b],id[b]).n[0];
				b=fa[b];
			}
			assert((n0<<1)==k-Seg::s[1].n[1]);
			printf("%d %d\n",a,b);
			match[a]=match[b]=1;
			assert(!dfs3(rt));
			return 0;
		}
		printf("%d %d\n",a,b);
		match[a]=match[b]=1;
		update(a),update(b);
		k-=d[a]+d[b];
		if(!(--du[fa[a]])) q[A].push(fa[a]);
		if(!(--du[fa[b]])) q[B].push(fa[b]);
		s.erase(make_pair(sA,A)),s.insert(make_pair(sA-1,A));
		s.erase(make_pair(sB,B)),s.insert(make_pair(sB-1,B));
	}
	return 0;
}

标签:Distance,匹配,sz,int,下界,Seg,CF1396E,data,Matching
From: https://www.cnblogs.com/ez-lcw/p/16837315.html

相关文章