[省选集训2022] 模拟赛11
定位系统
题目描述
\(n\) 个城市构成一棵树,现在要求在一些城市中设置监测点,使得每个城市可以通过到监测点的距离区分出来(不同可以知道是到哪个监测点的距离,可以类比为树上的坐标)
给定 \(q\) 次修改,每次断开边 \((u,v)\) 再连上边 \((x,y)\),然后求出最小设置的监测点数目。
解法
无根树问题考虑定根,我们先枚举根强制根选取,那么只有深度相同的点需要区分。设 \(f(x)\) 表示子树 \(x\) 内设置关键点的数目,如果 \(x\) 有多个子树,我们可能要在合并的时候多设置关键点才能区分子树,显然要求是至多一个子树内没有关键点:
接下来就是定根的问题了,一定要去找结论(我就是凭感觉找错了结论),考虑我们添加关键点的过程都是必要的,那么如果根是度数 \(\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 <cstdio>
#include <cassert>
#include <iostream>
#include <set>
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<pair<int,int>> 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<n;i++)
{
int u=read(),v=read();
d[u]++;d[v]++;link(u,v);
}
for(int i=1;i<=n;i++) add(i);
int q=read();print();
while(q--)
{
int u=read(),v=read(),x=read(),y=read();
cut(u,v);link(x,y);
rem(u);rem(v);d[u]--;d[v]--;add(u);add(v);
rem(x);rem(y);d[x]++;d[y]++;add(x);add(y);
print();
}
}
祖先
题目描述
给定一棵以 \(1\) 为根的有根树,\(i\) 号点有点权 \(v_i\),你需要进行 \(q\) 次操作,操作有 \(3\) 种:
- \(\tt S\):把 \(u\) 的点权加上 \(d\)
- \(\tt M\):把 \(u\) 子树所有点的点权加上 \(d\)
- \(\tt Q\):给定 \(u\),询问 \(\sum_{i=1}^n\sum_{j=i+1}^n[lca(i,j)=u]v_i\cdot v_j\)
\(n,q\leq 2\cdot 10^5\)
解法
首先考虑没有 \(\tt M\) 操作的情况,那么对于一次单点修改我们可以使用树链剖分,对于轻儿子就暴力修改算贡献,重儿子就打修改标记,然后在询问的时候算轻重儿子之间的贡献即可。
考虑 \(\tt M\) 操作,一个关键的 \(\tt observation\) 是:对于 \(u\) 的祖先,\(\tt M\) 操作可以等效为单点修改。对于子树内的节点,我们考虑维护出支持整体加 \(x\) 的函数 \(f(x)\),不难发现 \(f(x)\) 是一个二次函数。
那么我们在单点修改的时候维护 \(f(x)\) 即可,思路大体就是这样,具体实现细节很重要:
- 对于 \(0\) 次项的维护,不管修改的顺序如何,我们都是先考虑轻儿子,再考虑重儿子。相当于依次加入,加入时和以前的东西算下贡献即可。
- 对于 \(1\) 次项的维护,无论是轻儿子还是重儿子,修改点权的时候都需要和子树内所有点算贡献。轻儿子在暴力往上跳的时候维护,重儿子先打标记然后在询问的时候计算。
- \(2\) 次项是定值,可以在一开始的预处理中直接计算。
- 等效操作指的是 \(u\) 处进行 \(siz(u)\cdot d\) 的单点修改,但是这样需要把计算错误的部分减去。还有就是 \(u\) 既不算在轻儿子中又不算在重儿子中,单独提出来考虑会更方便计算。
时间复杂度 \(O(n\log^2n)\),打标记的操作都用树状数组维护。
#include <cstdio>
#include <vector>
using namespace std;
const int M = 200005;
#define ull unsigned long long
const ull p = (1ull<<63)-1;
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(ull x)
{
if(x>=10) write(x/10);
putchar(x%10+'0');
}
int n,m;vector<int> g[M];
int cnt,son[M],siz[M],top[M],num[M],fa[M];
ull a[M],sub[M],ls[M],l0[M],l1[M],x2[M];
struct node
{
ull b[M];
int lowbit(int x)
{
return x&(-x);
}
void upd(int x,ull c)
{
for(int i=x;i<=n;i+=lowbit(i))
b[i]+=c;
}
ull ask(int x)
{
ull r=0;
for(int i=x;i>0;i-=lowbit(i)) r+=b[i];
return r;
}
ull get(int l,int r)
{
return ask(r)-ask(l-1);
}
ull qry(int u)
{
return get(num[u],num[u]+siz[u]-1);
}
}A,B;
void dfs1(int u)
{
siz[u]=1;
for(int v:g[u])
{
fa[v]=u;dfs1(v);
x2[u]+=1ll*siz[v]*siz[u];
siz[u]+=siz[v];
if(siz[v]>siz[son[u]]) son[u]=v;
}
}
void dfs2(int u,int tp)
{
top[u]=tp;num[u]=++cnt;
if(son[u]) dfs2(son[u],tp);
for(int v:g[u]) if(v^son[u])
dfs2(v,v);
}
void add(int u,ull c)
{
int x=u;
while(fa[top[x]])
{
int to=top[x],y=fa[to];
ull tmp=A.qry(to);//query the sum of the light son
l0[y]+=c*(ls[y]-tmp);//the contri. among light son
l1[y]+=c*(siz[y]-siz[to]);//kx
ls[y]+=c;x=y;
}
a[u]+=c;A.upd(num[u],c);
}
ull qry(int x)
{
if(!son[x]) return 0;
ull ss=A.qry(son[x]),sx=A.qry(x);
//0
ull ans=l0[x]+ls[x]*ss+a[x]*(sx-a[x]);
ull d=B.get(1,num[x]);
//2
ans+=d*d*x2[x];
//1
ans+=d*l1[x];
ans+=d*ss*(siz[x]-siz[son[x]]);
ans+=d*a[x]*(siz[x]-1);
//subtract the error part
ans-=sub[x]*(sx-a[x]+d*(siz[x]-1));
return ans&p;
}
signed main()
{
freopen("ancestor.in","r",stdin);
freopen("ancestor.out","w",stdout);
n=read();m=read();char s[5];
for(int i=2;i<=n;i++)
g[read()].push_back(i);
dfs1(1);dfs2(1,1);
for(int i=1;i<=n;i++)
add(i,read());
while(m--)
{
scanf("%s",s);
if(s[0]=='S')
{
int u=read();ull d=read();
add(u,d);
}
if(s[0]=='M')
{
int u=read();ull d=read();
add(u,d*siz[u]);sub[u]+=d*siz[u];
B.upd(num[u],d);
B.upd(num[u]+siz[u],-d);
}
if(s[0]=='Q')
write(qry(read())),puts("");
}
}