Title

CF1338B Edge Weight Assignment 题解

解题思路

一种不需要树形 dp 的做法。

因为是一颗无根树,所以我们不妨以重心为根。

首先考虑边权最少的情况。可以发现这个时候对于任意两个叶子节点,我们可以分别考虑其到根节点的路径,这样对于求其路径的异或值是没有影响的,但是在这种情况下我们可以很方便的讨论其路径的奇偶性。令 \(d(x,y)\) 表示从 \(x\)\(y\) 的深度之差,\(root\) 表示根节点,那么对于任意两个叶子节点 \(u\)\(v\),情况如下:

  • \(d(u,root)\) 为奇,\(d(v,root)\) 为奇,那么可以构造两条相同的路径使得其中边权均为相同的数,此时需要 \(1\) 种异或值;
  • \(d(u,root)\) 为偶,\(d(v,root)\) 为奇,那么这个时候可以构造其中 \(u\)\(root\) 的路径上有两种边权,不妨设它们为 \(w_1\)\(w_2\),且其中一种边权出现一次,剩下的均为另一种边权,同时,我们把 \(v\)\(root\) 的路径上的边全部赋为 \(w_1\oplus w_2\),这种情况下,我们最少使用 \(3\) 种权值;
  • \(d(u,root)\) 为奇,\(d(v,root)\) 为偶,同上;
  • \(d(u,root)\) 为偶,\(d(v,root)\) 为偶,同样可以将 \(u\)\(root\) 的路径上的边和 \(v\)\(root\) 上的边全部赋为同一种权值,这时使用 \(1\) 种权值。

接下来考虑边权最多的情况。不难发现,拥有同一个父亲节点的叶子节点,它们到父亲节点的边权一定是相同的,那么我们可以考虑合并这些叶子节点。合并后观察可以得出,因为边权可以为任意正整数,那么一定存在一种构造方案使得任意两个叶子节点之间路径的异或值为 \(0\) 且合并后剩余的每条边的边权均不相同。

综上所述,本题解题步骤如下:

  • 计算出重心,当然也可以选择其他不是叶子节点的点作为根节点;
  • 计算所有叶子节点到根节点的距离,此时的距离等于两点之间的深度差的绝对值;
  • 判断所有距离的奇偶性,若所有距离的奇偶性相同,那么最小值为 \(1\),否则为 \(3\)
  • 因为合并后一个节点最多连向一个叶子节点,那么我们剩余的边数即为 \(\displaystyle n-1-\sum_{i=1}^n (cnt_{leaves} -1)\),在计算距离的时候顺便判断计算一下即可。

AC 代码

#include<stdio.h>
#include<vector>
#include<algorithm>
#define N 100005
#define inf 2e9+7
int n;
std::vector<int> edge[N];
int root=1,sum;
int maxt[N],size[N];
inline void FindRoot(int u,int fa){
    size[u]=1;
    for(auto v:edge[u]){
        if(v==fa) continue;
        FindRoot(v,u);
        size[u]+=size[v];
        if(size[v]>maxt[u])
            maxt[u]=size[v];
    }if(sum-size[u]>maxt[u])
        maxt[u]=sum-size[u];
    if(maxt[u]<maxt[root])
        root=u;
}
int depth[N],lea[N],cnt,tot[N];
inline void dfs(int u,int fa){
    bool isLeavf=true;
    for(auto v:edge[u]){
        if(v==fa) continue;
        depth[v]=depth[u]+1;
        dfs(v,u);isLeavf=false;
    }if(isLeavf){
        lea[++cnt]=u;
        ++tot[fa];
    }
}
signed main(){
    scanf("%d",&n);int u,v;
    for(register int i=1;i<n;++i){
        scanf("%d%d",&u,&v);
        edge[u].push_back(v);
        edge[v].push_back(u);
    }sum=n;maxt[root]=inf;
    FindRoot(1,0);dfs(root,0);
    int odd=0,eve=0,maxt=0,sema=0;
    for(register int i=1;i<=cnt;++i){
        if(depth[lea[i]]&1)
            ++odd;
        else ++eve;
    }if(!odd||!eve)
        putchar('1');
    else putchar('3');
    putchar(' ');int sub=0;
    for(register int i=1;i<=n;++i)
        sub+=tot[i]?tot[i]-1:0;
    printf("%d",n-1-sub);
}
posted @ 2024-02-07 10:06  UncleSam_Died  阅读(17)  评论(0)    收藏  举报