P7165 [COCI 2020/2021 #1] Papričice 做题报告

前言

这一部分会在 luogu 博客里面删掉

很好的题目让我在模拟赛里面旋转
感觉 luogu 的难度评级越来越难了,新出的题目有的黄都做不出,之前的古董可能能轻松出紫
可能是实力变菜了吧,这个题目个人认为如果不去使用 STL 的 set 是很好的树上问题,落实好了可以提升多方面的能力
没想到居然能在模拟赛上把这个题目做出来,虽然这个题目放在了第二题,但是我第一题没做出来哈哈哈
好了接下来开始正题

题意

给一个树,让你选择两条边将其断开,使得形成的三个连通块大小中最大值减去最小值最小。

思考

如你所见,这个题解是一个不使用 STL 中的 set 的题解。
首先我们是没有任何思路的,考虑怎么 \(n^3\) 做,可以直接暴力枚举边的位置,然后暴力去寻找连通块。
然后考虑怎么降为 \(n^2\),下文中如果是将 \(x\) 的父边和 \(y\) 的父边切断,则记为切断 \((x,y)\),那么我们决定分类讨论:

  • 1.如果 \((x,y)\) 之间存在父子关系,记 \(y\)\(x\) 的祖先节点,那么切断了 \((x,y)\) 后,三个部分的大小分别为 \(\{siz_x,siz_y-siz_x,n-siz_y\}\)
  • 2.否则 \((x,y)\) 不存在任何关系,三个部分的大小为 \(\{siz_x,siz_y,n-siz_x-siz_y\}\)
    那么我们可以直接暴力枚举 \(x,y\),然后判断它们之间的情况即可。

好的接下来才是正题:
我们思考怎么做到 \(O(n\log_2n)\) 或者 \(O(n\log_2^2n)\)
首先我们依次处理两种情况,第一种情况可以先暴力枚举 \(x\),接下来就相当于要找到一个 \(y\) 使得 \(siz_y\)\(siz_x-siz_y\) 之间的最大值尽可能的小,并且 \(y\)\(x\) 的子树里面。那我们可以继续分类,将 \(siz_y\) 分成 \(siz_y\le \lfloor \frac{siz_x}{2}\rfloor\)\(siz_y> \lfloor \frac{siz_x}{2}\rfloor\),考虑 \(x\) 的直系儿子,他的直系儿子中肯定不会有两个及以上的 \(y\) 满足 \(siz_y> \lfloor \frac{siz_x}{2}\rfloor\),所以对于所有不大于 \(siz_x\) 的一半的儿子,\(siz_x-siz_y\) 一定比 \(siz_y\) 更大,所以直接将 \(siz_y\) 记录答案即可,现在我们需要面对的是超过了 \(siz_x\) 一半了的儿子。
这个部分我们最暴力的做法是一个一个往下跳,找到第一个 \(siz_y\le \lfloor \frac{siz_x}{2}\rfloor\),但是这样一定会超时,所以我们可以倍增,但是倍增又不能向下跳,所以我们可以重链剖分一下,那么在这条链的链底到 \(x\) 的路径上一定存在我们要找的点,倍增跳找到这个点即可。

好的接下来才是真正的恶战:
先思考,如果我们已经决定了 \(a\),要找到一个 \(b,c\) 满足 \(b\in siz,b+c=n-a\) 并且 \(\max(a,b,c)-\min(a,b,c)\) 最小要怎么办,首先满足最后一个条件一定是 \(b,c\) 之间的最大值最小,那么既然 \(b\in siz\),所以我们可以从 \(\frac{n-a}{2}\) 开始,二分一个数字 \(k\),表示从 \(\frac{n-a}{2}\) 向左向右 \(k\) 个内有没有一个数字 \(b\in siz\),那如何判断是否存在这样的一个 \(b\) 呢,因为我们还要同时维护修改操作,所以可以尝试使用树状数组,每一次修改就插入一个数字,询问的时候二分 \(k\),判断这个长度 \(2\times k\) 的数列到底有没有数字。
我们考虑第二种情况,\((x,y)\) 之间没有关系的情况,因为我们一开始的时候钦定了 \(y\)\(x\) 的祖先,所以我们必须从 \(y\) 节点的子树里面找到 \(x\),但是 \((x,y)\) 之间没有祖先关系的时候,我们可以找到 \((x,y)\) 的答案,也可以找到 \((y,x)\) 的答案,所以对于一个节点,他的任意两个不同的儿子节点的子树里面的节点一定可以互相匹配,所以对于每一个节点,进入的时候查询一下 \(\max(siz_x,siz_y,n-siz_x-siz_y)-\min(siz_x,siz_y,n-siz_x-siz_y)\) 的最小值,然后递归子节点,离开的时候将这个节点插入即可。可能有点难调,但是这是一个不用 set 的做法。

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cmath>
#include<climits>
#include<stdio.h>
#include<vector>
#define ll long long
#define lowbit(x) (x&(-x))

using namespace std;
const int N=4e5+9;
int ed[N],hson[N],siz[N],n,fa[N][22],book[N],dep[N];
int mindel[N],tans;
vector<int>e[N];

inline void change(int x){
    for(;x<=2*n;x+=lowbit(x))
        book[x]++;
}
inline int query(int x){
    if(x<=0) return 0;
    int sum=0;
    for(;x;x-=lowbit(x))
        sum+=book[x];
    return sum;
}

inline void dfs_gethson(int x,int f){
    fa[x][0]=f;
    dep[x]=dep[f]+1;
    siz[x]=1;
    for(int i=1;i<=21;i++)
        fa[x][i]=fa[fa[x][i-1]][i-1];
    for(auto i:e[x]){
        if(i==f) continue;
        dfs_gethson(i,x);
        siz[x]+=siz[i];
        if(siz[hson[x]]<siz[i])
            hson[x]=i;
    }
}
inline int getend(int x){
    if(hson[x]) ed[x]=getend(hson[x]);
    else ed[x]=x;
    for(int i:e[x])
        if(i!=fa[x][0] && i!=hson[x])
            getend(i);
    if(hson[x]) return ed[x];
    else return x;
}
inline void dfs_getmindel(int x){
    int flag=0;
    mindel[x]=INT_MAX;
    for(int i:e[x]){
        if(i==fa[x][0]) continue;
        dfs_getmindel(i);
        if(siz[i]>siz[x]/2) flag=1;
        else{
            if(mindel[x]==INT_MAX || siz[x]-2*siz[mindel[x]]>siz[x]-2*siz[i])
                mindel[x]=i;
        }
    }
    if(flag==0) return void();
    int now=ed[x];
    for(int i=21;i>=0;i--){
        if(siz[fa[now][i]]<=siz[x]/2)
            now=fa[now][i];
    }
    if(mindel[x]==INT_MAX || abs(siz[now]*2-siz[x])<siz[x]-2*siz[mindel[x]])
        mindel[x]=now;
    now=fa[now][0];
    if(mindel[x]==INT_MAX || abs(siz[now]*2-siz[x])<siz[x]-2*siz[mindel[x]])
        mindel[x]=now;
}
inline void dfs_getans(int x){
    if(e[x].size()==1 && x!=1){
        change(1);
        return void();
    }
    int lt=0,rt=n,ans=-1,l=0,r=0;
    int jz=(n-siz[x])/2;
    rt=jz;rt=min(rt,n-jz);
    while(lt<=rt){
        int mid=lt+rt>>1;
        int l=jz-mid,r=jz+mid+((n-siz[x])&1);
        if(query(r)-query(l-1)) ans=mid,rt=mid-1;
        else lt=mid+1;
    }
    l=(n-siz[x])/2-ans,r=(n-siz[x])/2+ans+((n-siz[x])&1);
    if(ans!=-1)tans=min(tans,max({siz[x],l,r})-min({siz[x],l,r}));
    for(int i:e[x]){
        if(i==fa[x][0]) continue;
        dfs_getans(i);
    }
    change(siz[x]);
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);

    cin>>n;
    for(int i=1;i<n;i++){
        int u,v;
        cin>>u>>v;
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs_gethson(1,1);
    getend(1);
    dfs_getmindel(1);
    int ans=INT_MAX;
    for(int i=1;i<=n;i++){
        if(mindel[i]==INT_MAX) continue;
        ans=min(ans,max({siz[mindel[i]],siz[i]-siz[mindel[i]],n-siz[i]})-min({siz[mindel[i]],siz[i]-siz[mindel[i]],n-siz[i]}));
    }
    tans=ans;
    dfs_getans(1);
    cout<<tans;
    return 0;
}

posted @ 2025-10-11 15:38  zacharyzhongyq  阅读(9)  评论(0)    收藏  举报