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]);
}