[省选集训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 <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("");
	}
}
posted @ 2022-02-24 17:27  C202044zxy  阅读(211)  评论(0编辑  收藏  举报