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

浙公网安备 33010602011771号