[省选集训2022] 模拟赛11
定位系统
题目描述
\(n\) 个城市构成一棵树,现在要求在一些城市中设置监测点,使得每个城市可以通过到监测点的距离区分出来(不同可以知道是到哪个监测点的距离,可以类比为树上的坐标)
给定 \(q\) 次修改,每次断开边 \((u,v)\) 再连上边 \((x,y)\),然后求出最小设置的监测点数目。
解法
无根树问题考虑定根,我们先枚举根强制根选取,那么只有深度相同的点需要区分。设 \(f(x)\) 表示子树 \(x\) 内设置关键点的数目,如果 \(x\) 有多个子树,我们可能要在合并的时候多设置关键点才能区分子树,显然要求是至多一个子树内没有关键点:
\[f(x)=\sum_{y\in son_x} \max(f(y),1)-[\prod_{y\in son_x} f(y)=0] \]接下来就是定根的问题了,一定要去找结论(我就是凭感觉找错了结论),考虑我们添加关键点的过程都是必要的,那么如果根是度数 \(\geq 3\) 的点,那么根是不需要选取的,并且由于其他点的选取是必要的,我们得到了最优解。
然后考虑这个修改显然就是让你写 \(\tt lct\) 维护 \(\tt ddp\) 了,这题一个很强的操作是维护分段函数。
具体来说就是自变量 \(=0\),那么对应函数 \(A\)(一次项系数为 \(0\));自变量 \(>0\) 又对应着函数 \(B\)(一次项系数为 \(1\)),那么函数如何合并呢?考虑两个分段函数 \(u,v\) 按顺序合并,我们把 \(v_A\) 带入 \(u\) 的分段函数中得到新的 \(A\),由于 \(f\) 是单调的,所以大于 \(0\) 之后不会再变回 \(0\),那么新的 \(B\) 就是 \(u_B+v_B\) 了。
但是本题由于要定根所以还要写 makeroot
,所以你需要维护正序和逆序的函数。
此外注意重链底端的函数是没有定义的,所以只能直接拿值,那么我们在求某个重链的值时,把最低点 splay
到根,然后把判断是否有左儿子,如果有则带值进左儿子的函数,如果没有则用轻儿子的信息计算。
#include
#include
#include
#include
using namespace std;
const int M = 500005;
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;
}
void write(int x)
{
if(x>=10) write(x/10);
putchar(x%10+'0');
}
int n,d[M],ch[M][2],fa[M],sum[M],num[M],st[M],fl[M];
multiset> s;
struct node
{
int a,b;
node(int A=0,int B=0) : a(A) , b(B) {}
int get(const int &x) const {return !x?b:x+a;}
node operator & (const node &r) const
{return node(a+r.a,get(r.b));}
}l[M],r[M];
void up(int x)
{
if(!x) return ;
l[x]=r[x]=node(sum[x]-(num[x]>0),sum[x]);
if(ch[x][0])
{
l[x]=l[ch[x][0]]&l[x];
r[x]=r[x]&r[ch[x][0]];
}
if(ch[x][1])
{
l[x]=l[x]&l[ch[x][1]];
r[x]=r[ch[x][1]]&r[x];
}
}
void flip(int x)
{
if(!x) return ;
swap(l[x],r[x]);fl[x]^=1;
swap(ch[x][0],ch[x][1]);
}
void down(int x)
{
if(!x || !fl[x]) return ;
flip(ch[x][0]);
flip(ch[x][1]);
fl[x]=0;
}
int nrt(int x)
{
return ch[fa[x]][0]==x || ch[fa[x]][1]==x;
}
int chk(int x)
{
return ch[fa[x]][1]==x;
}
void rotate(int x)
{
int y=fa[x],z=fa[y],k=chk(x),w=ch[x][k^1];
ch[y][k]=w;fa[w]=y;
if(nrt(y)) ch[z][chk(y)]=x;fa[x]=z;
ch[x][k^1]=y;fa[y]=x;
up(y);up(x);
}
void splay(int x,int tar=0)
{
if(!x) return ;
int y=x,z=0;st[++z]=x;
while(nrt(y)) st[++z]=y=fa[y];
while(z) down(st[z--]);
while(nrt(x) && fa[x]!=tar)
{
int y=fa[x];
if(nrt(y) && fa[y]!=tar)
{
if(chk(x)==chk(y)) rotate(y);
else rotate(x);
}
rotate(x);
}
}
int calc(int x,int fa=0)
{
splay(x,fa);
while(ch[x][1]) down(x),x=ch[x][1];
splay(x,fa);
int t=sum[x]-(num[x]>0);
if(!ch[x][0]) return t;
return l[ch[x][0]].get(t);
}
void access(int x)
{
for(int y=0,tmp=0;x;x=fa[y=x])
{
splay(x);
if(ch[x][1])
{
tmp=calc(ch[x][1],x);
sum[x]+=max(1,tmp);num[x]+=!tmp;
}
if(y)
{
tmp=calc(y);
sum[x]-=max(1,tmp);num[x]-=!tmp;
}
splay(y);ch[x][1]=y;up(x);
}
}
void makert(int x)
{
access(x);splay(x);flip(x);
}
void cut(int x,int y)
{
makert(x);access(y);
splay(x);splay(y,x);
ch[x][1]=fa[y]=0;up(x);
}
void link(int x,int y)
{
makert(x);access(y);splay(y);
fa[x]=y;ch[y][1]=x;up(y);
}
void add(int x) {s.insert(make_pair(d[x],x));}
void rem(int x) {s.erase(make_pair(d[x],x));}
void print()
{
if(n==1) puts("0");
else if(s.rbegin()->first<=2) puts("1");
else
{
int x=s.rbegin()->second;
makert(x);printf("%d\n",calc(x));
}
}
signed main()
{
freopen("location.in","r",stdin);
freopen("location.out","w",stdout);
n=read();
for(int i=1;i