CodeForces 1396E Distance Matching(构造+树的重心+dfs+set)

problem

洛谷链接

solution

这种要求值和恰好为 k k k 的题目,一般要先明确值和的取值范围。

所以我们先来确定一下值和的最小值和最大值。

将一条路径拆成若干条边,单独计算每条边对路径的贡献。

假设一条边将树划分成 S , T S,T S,T 集合。因为 n n n 为偶数,所以 S , T S,T S,T 同奇偶。

则贡献最大值就是每条路径都经过该边,最小值就是尽量内部消化。

每个点只能匹配一个点,故最大值为 min ⁡ ( ∣ S ∣ , ∣ T ∣ ) \min(|S|,|T|) min(S,T),最小值为 ∣ S ∣ & 1 |S|\& 1 S&1

这个 min ⁡ \min min 我们想办法去掉, ∣ S ∣ + ∣ T ∣ = n ⇒ min ⁡ ( ∣ S ∣ , ∣ T ∣ ) ≤ n 2 |S|+|T|=n\Rightarrow \min(|S|,|T|)\le \frac n 2 S+T=nmin(S,T)2n

啊——重心。其定义就是任意一个亲儿子子树的大小都满足 s i z [ x ] ≤ n − s i z [ x ] siz[x]\le n-siz[x] siz[x]nsiz[x]

即值和最大值就是尽量所有路径都过重心, i ↔ i + n 2 i\leftrightarrow i+\frac n 2 ii+2n dfn \text{dfn} dfn 序配对。

不妨从最大值考虑下放成 k k k

如图,假设原先是 1 ↔ v , 2 ↔ u ⇒ 1 ↔ 2 , u ↔ v 1\leftrightarrow v,2\leftrightarrow u\Rightarrow 1\leftrightarrow 2,u\leftrightarrow v 1v,2u12,uv,这样子就做到了对答案减少 2 ∗ d e p [ l c a ( u , v ) ] 2*dep[lca(u,v)] 2dep[lca(u,v)] 的效果。

在这里插入图片描述

还发现,每次贡献的改变都是偶数,所以如果 k k k 与最大值不同奇偶也是无解的。

像倍增一样的,从大往小放缩。

找到一个深度最深的且子树大小(含自己)至少为 2 2 2 的点。

先考虑让这个点的子树内产生一对内部匹配,那么减少的贡献就是最大的。

  • 如果减少这个贡献,还没有下放到 k k k,就继续上面的操作。
  • 如果下放过了,因为路径肯定是连续的,那么从重心到这个点深度层中一定恰好有一个点能恰好使得最后下放到 k k k

具体而言,构造如下:

  • k = M a x k=Max k=Max,直接 dfn \text{dfn} dfn 序后, i ↔ i + n 2 i\leftrightarrow i+\frac n 2 ii+2n

  • k < M a x k<Max k<Max。假设重心为 r o o t root root

    r o o t root root 亲儿子中子树大小最大的点 x x x,则 s i z [ x ] ≥ 2 siz[x]\ge 2 siz[x]2

    x x x 后代中最深的且子树大小至少为 2 2 2 的点 y y y

    • 2 d e p [ y ] < M a x − k 2dep[y]< Max-k 2dep[y]<Maxk,可以选择两个 l c a = y lca=y lca=y 的点内部匹配并删除, M a x Max Max k k k 的差距缩小 2 d e p [ y ] 2dep[y] 2dep[y],回到上一步。

    • 2 d e p [ y ] ≥ M a x − k 2dep[y]\ge Max-k 2dep[y]Maxk,这一条路径上一定有一个点 z , d e p [ z ] = M a x − k 2 z,dep[z]=\frac{Max-k}{2} z,dep[z]=2Maxk,同样选择两个 l c a = z lca=z lca=z 的点内部匹配。break 整个过程。

      set 存子树内的深度及点编号,到时候一个 lower_bound 查即可。

    当然这个点可以是 y , z y,z y,z 本身。

过程中的匹配变化, k < M a x , 2 d e p [ y ] ≤ M a x − k k<Max,2dep[y]\le Max-k k<Max,2dep[y]Maxk,不会让重心移动,所以不用每次都重新求,set 模拟维护。

具体而言,算法流程如下:

  • dfs1 获得重心以及值和的上下界。迅速特判 k k k 能够被构造出来。

  • 对重心的每个亲儿子分别进行子树 dfs2s[] 记录子树内每个点的深度,子树大小,未配对儿子个数,隶属于重心的亲儿子的编号。深度为第一关键字。

    只有这个亲儿子子树大小 ≥ 2 \ge 2 2 的时候,才能用于后面的匹配下放,才放入候选方案 g 中。子树大小为第一关键字。

  • 从最大亲儿子 x x x 开始考虑,用 lower_bound 找构造中的 y y y,并进行判断。

    进行信息修正。 x x x 子树中能匹配的个数减少 2 , s i z [ x ] − = 2 2,siz[x]-=2 2,siz[x]=2。随便找两个 y y y 内未匹配的点配对输出,打上已配对标记。

    重复本条流程直到跳出。

  • 剩下的未匹配点就是普通的 i ↔ i + n 2 i\leftrightarrow i+\frac n 2 ii+2n 配对,dfs3 找所有未匹配点(这些点可能已经被一些匹配点割裂成不联通的几个部分了)。

code

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define maxn 100005
set < pair < int, int > > s[maxn], g;
vector < int > G[maxn], id;
int n, k;
int center = 1e18, root, Min, Max;
bool vis[maxn];
int f[maxn], son_cnt[maxn], siz[maxn], dep[maxn], belong[maxn];

void dfs1( int u, int fa ) {
    int maxson = 0; siz[u] = 1;
    for( int v : G[u] ) {
        if( v == fa ) continue;
        dfs1( v, u );
        siz[u] += siz[v];
        maxson = max( maxson, siz[v] );
    }
    maxson = max( maxson, n - siz[u] );
    if( maxson < center ) center = maxson, root = u;
    if( u ^ 1 ) Min += siz[u] & 1, Max += min( siz[u], n - siz[u] );
}

void dfs2( int u, int fa, int t ) {
    belong[u] = t, dep[u] = dep[fa] + 1, siz[u] = 1, f[u] = fa;
    for( int v : G[u] ) {
        if( v == fa ) continue;
        else dfs2( v, u, t );
        son_cnt[u] ++, siz[u] += siz[v];
    }
    if( son_cnt[u] ) s[t].insert( make_pair( dep[u], u ) );
}

void dfs3( int u, int fa ) {
    if( ! vis[u] ) id.push_back( u );
    for( int v : G[u] ) if( v ^ fa ) dfs3( v, u );
}

void _delete( int x ) {
    vis[x] = 1;
    son_cnt[f[x]] --;
    if( ! son_cnt[f[x]] ) s[belong[f[x]]].erase( make_pair( dep[f[x]], f[x] ) );
}

void match( int x ) {
    vector < int > use;
    for( int i : G[x] ) if( i ^ f[x] and ! vis[i] ) use.push_back( i );
    if( ! vis[x] ) use.push_back( x );
    printf( "%lld %lld\n", use[0], use[1] );
    _delete( use[0] ), _delete( use[1] );
}

signed main() {
    scanf( "%lld %lld", &n, &k );
    for( int i = 1, u, v;i < n;i ++ ) {
        scanf( "%lld %lld", &u, &v );
        G[u].push_back( v );
        G[v].push_back( u );
    }
    dfs1( 1, 0 );
    if( k < Min or Max < k or ( Max - k ) & 1 ) return ! printf( "NO\n" );
    else printf( "YES\n" );
    for( int x : G[root] ) {
        dfs2( x, root, x );
        if( siz[x] > 1 ) g.insert( make_pair( siz[x], x ) );
    }
    int rest = Max - k;
    while( rest ) {
        int x = g.rbegin() -> second;
        g.erase( make_pair( siz[x], x ) );
        int y = s[x].rbegin() -> second;
        if( ( dep[y] << 1 ) >= rest ) {
            y = s[x].lower_bound( make_pair( rest >> 1, 0 ) ) -> second;
            match( y );
            break;
        }
        else {
            rest -= ( dep[y] << 1 );
            siz[x] -= 2;
            match( y );
            if( siz[x] > 1 ) g.insert( make_pair( siz[x], x ) );
        }
    }
    dfs3( root, 0 );
    for( int i = 0;i < ( id.size() >> 1 );i ++ )
        printf( "%lld %lld\n", id[i], id[i + ( id.size() >> 1 )] );
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值