• [省选集训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("");
    	}
    }
    
  • 相关阅读:
    原码, 反码, 补码 详解
    位移运算符
    ASP.NET中httpmodules与httphandlers全解析
    MySQL count
    真正的能理解CSS中的line-height,height与line-height
    IfcEvent
    IfcWorkCalendarTypeEnum
    IfcSingleProjectInstance
    转换模型
    IfcTypeProduct
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15932775.html
Copyright © 2020-2023  润新知