题解 Andeviking 开公司 (hard)

发布时间 2023-06-03 18:27:48作者: caijianhong

problem

链接:https://ac.nowcoder.com/acm/contest/59248/J
来源:牛客网

Andeviking 受够了打工人的生活。为了体验一下老板的感觉,他准备自己开一家公司,而一个公司能够高效运转的前提是该公司有一个高效的财务系统。由于 Andeviking 难以接受市面上现有系统的低效,他想让你这个水平高超的 acmer 帮他设计一套财务系统。

公司的人员按照职位高低可以构成一棵有根树,其中 1 号节点为根节点。每个节点的子节点可以看作是该节点的员工,每个节点也可以被看作是该节点的子节点的上司。每个人能且仅能修改自己员工的工资(他当然不可以自己给自己发工资)。每个人在公司中的等级定义为他上司的个数(上司的个数可以看作该点在树上的深度,其中 1 号节点的深度为 0),每人的初始工资将由输入给出 。该财务系统需要支持以下五种操作:

  • 将编号为 p 的人的工资改为 x
  • 编号为 p 的人将自己员工中比他恰好低 k 级的员工的工资全部改为 x ,若不存在这样的员工,则忽略此次操作
  • 编号为 p 的人将自己员工中比他恰好低 k 级的员工的工资全部增加或减少 x,正数代表增加,负数代表减少,0 代表不变,若不存在这样的员工,则忽略此次操作
  • 查询编号为 p 的人的工资
  • 查询编号为 p 的人的员工中比他恰好低 k 级的员工的工资和,若不存在这样的员工,输出 0

(请不要惊讶于工资可以是负数,作为一位凉心资本家,Andeviking 这样做是很正常的)

Andeviking 的耐心是有限的,他希望你在五小时内设计出满足要求的系统,作为报酬他会让志愿者送你一个气球。作为一个强大的 acmer ,请你帮帮 Andeviking 吧。

\(n,q,x,k\leq 10^5\)

solution

考虑将树拍成 bfs 序,维护 bfs 序。然后用一个支持区间覆盖,区间加,区间求和的数据结构维护它。

我们发现难点在于怎么找到 \(p\) 的子树中比它低 \(k\) 级的点对应的 bfs 序区间,我们可以长链剖分。

长链剖分

参考文章:https://www.cnblogs.com/maoyiting/p/14178833.html

与重链剖分相似,长链剖分是将树剖成很多条长链。

我们定义长儿子,为一个点的儿子中子树深度最大的一个儿子。或者这样定义:

  • 给每个点一个 \(height_u\)。叶子节点的 \(height\)\(1\)
  • 则其它所有点的 \(height_u\) 定义为 \(\max_{v\in son}\{height_v\}+1\),并记录取到 \(height_u\) 的儿子 \(v\) 为长儿子(保留一个)
  • 长链就是一条到叶节点的链,满足链上任意一个父亲的儿子是它的长儿子。

有几个性质值得留意:

  • 所有长链长度总和为 \(O(n)\),因为每一个点都只在一个长链中。
  • 任意一个节点 \(x\)\(k\) 级祖先 \(y\) 所在长链的长度一定大于等于 k,否则 \(y\) 所在长链应该接到 \(x\) 上去。
  • 从一个节点开始向上跳长链,最多跳 \(O(\sqrt{n})\) 次。因为每次跳到的长链长度一定是单调递增的。

长链剖分可以 \(O(n\log n)-O(1)\) 求 LCA,这里我不写了。

长链剖分优化 DP

重写一遍问题:求出 \(p\) 向下 \(k\) 层的子节点在 bfs 序上的编号(明显连续),询问很多次。

那么这个问题有暴力做法:\(f_{u,k}\) 表示答案,然后转移时将儿子们合并起来。\(O(n^2)\)

长链剖分优化与深度有关的 DP,一句话就是:继承长儿子的答案,暴力合并轻儿子。这样的时间复杂度是线性,因为每条链都在链顶上被暴力合并。

具体地,你 \(solve(u)\) 的时候,如果有长儿子,那么 \(solve(son_u)\),把长儿子的信息拿过来,并做一次位移以继承,然后枚举轻儿子大力合并。

这个位移有两种做法:一是 vector,反过来存,push_back 一下;二是指针,在链顶开数组,在 \(solve(son_u)\) 时把 \(son_u\) 的 DP 数组定义为 \(u\) 右移一位,即 \(f_{son_u}=f_u+1\)(指针加法)。

那么就做完了。

code

写起来很劲爆,推荐一写。也就 4 KB。

点击查看代码
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr,##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
typedef long long LL;
struct Tag{LL mul,add;};
struct Ans{LL len,ans;};
Tag operator-(Tag a,Tag b){return Tag{a.mul*b.mul,a.add*b.mul+b.add};}
Ans operator+(Ans a,Ans b){return Ans{a.len+b.len,a.ans+b.ans};}
Ans operator*(Ans a,Tag b){return Ans{a.len,a.ans*b.mul+a.len*b.add};}
template<int N> struct segtree{
	Tag tag[N<<2]; Ans ans[N<<2];
	void maintain(int p){ans[p]=ans[p<<1]+ans[p<<1|1];}
	void add(Tag k,int p){ans[p]=ans[p]*k,tag[p]=tag[p]-k;}
	void pushdown(int p){add(tag[p],p<<1),add(tag[p],p<<1|1),tag[p]={1,0};}
	void build(int p,int l,int r){
		tag[p]={1,0};
		int mid=(l+r)>>1;
		if(l==r) ans[p]={1,0};
		else build(p<<1,l,mid),build(p<<1|1,mid+1,r),maintain(p);
	}
	void modify(Tag k,int L,int R,int p,int l,int r){
		if(L==-1) return ;
		if(L<=l&&r<=R) return add(k,p);
		int mid=(l+r)>>1; pushdown(p);
		if(L<=mid) modify(k,L,R,p<<1,l,mid);
		if(mid<R) modify(k,L,R,p<<1|1,mid+1,r);
		maintain(p);
	}
	Ans query(int L,int R,int p,int l,int r){
		if(L==-1) return {0,0};
		if(L<=l&&r<=R) return ans[p];
		int mid=(l+r)>>1; Ans res={0,0}; pushdown(p);
		if(L<=mid) res=res+query(L,R,p<<1,l,mid);
		if(mid<R) res=res+query(L,R,p<<1|1,mid+1,r);
		return res;
	}
};
int n,Q,a[1<<17],siz[1<<17],hei[1<<17],son[1<<17],dep[1<<17],dfn[1<<17];
vector<int> g[1<<17],cb[1<<17];
vector<pair<int,int>> que[1<<17];
struct ask{int op,l,r,x;} q[1<<17];
void dfs(int u,int fa=0){
	dep[u]=dep[fa]+1,siz[u]=1;
	cb[dep[u]].push_back(u);
	for(int v:g[u]) if(v!=fa){
		dfs(v,u);
		siz[u]+=siz[v];
		if(hei[son[u]]<hei[v]) son[u]=v;
	}
	hei[u]=hei[son[u]]+1;
}
void bfs(){
	int cnt=0;
	for(int i=1;i<=n;i++){
		for(int u:cb[i]) dfn[u]=++cnt;
	}
}
typedef pair<int,int> segm;
segm merge(segm a,segm b){
	return {min(a.first,b.first),max(a.second,b.second)};
}
segm* f[1<<17],tmp[1<<17],*now=tmp;
void solve(int u,int fa=0){
	f[u][0]={dfn[u],dfn[u]};
	if(son[u]) f[son[u]]=f[u]+1,solve(son[u],u);
	for(int v:g[u]) if(v!=fa&&v!=son[u]){
		f[v]=now,now+=hei[v],solve(v,u);
		for(int i=0;i<hei[v];i++) f[u][i+1]=merge(f[u][i+1],f[v][i]);	
	}
	for(auto p:que[u]){
		if(p.first<hei[u]){
			q[p.second].l=f[u][p.first].first;
			q[p.second].r=f[u][p.first].second;
		}else q[p.second].l=-1;
	}
}
/*
vector<segm> f[1<<17];
void solve(int u,int fa=0){
    if(son[u]) solve(son[u],u),swap(f[u],f[son[u]]);
    f[u].push_back({dfn[u],dfn[u]});
    auto get=[&](int u,int k)->segm&{return f[u][hei[u]-k-1];};
    for(int v:g[u]) if(v!=fa&&v!=son[u]){
        solve(v,u);
        for(int i=0;i<hei[v];i++) get(u,i+1)=merge(get(u,i+1),get(v,i)); 
    }
    for(auto p:que[u]){
        if(p.first<hei[u]){
            q[p.second].l=get(u,p.first).first;
            q[p.second].r=get(u,p.first).second;
        }else q[p.second].l=-1;
    }
}
*/
segtree<1<<17> t;
int main(){
//	#ifdef LOCAL
//	 	freopen("input.in","r",stdin);
//	#endif
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),g[u].push_back(v),g[v].push_back(u);
	dfs(1),bfs();
	scanf("%d",&Q);
	for(int i=1,p,k;i<=Q;i++){
		scanf("%d",&q[i].op);
		switch(q[i].op){
			case 1: scanf("%d%d",&p,&q[i].x),q[i].l=q[i].r=dfn[p]; break;
			case 4: scanf("%d",&p),q[i].l=q[i].r=dfn[p]; break;
			case 2: scanf("%d%d%d",&p,&k,&q[i].x),que[p].emplace_back(k,i); break;
			case 3: scanf("%d%d%d",&p,&k,&q[i].x),que[p].emplace_back(k,i); break;
			case 5: scanf("%d%d",&p,&k),que[p].emplace_back(k,i); break;
		}
	}
	f[1]=now,now+=hei[1],solve(1),t.build(1,1,n);
	for(int i=1;i<=n;i++) t.modify({0,a[i]},dfn[i],dfn[i],1,1,n);
	for(int i=1;i<=Q;i++){
		LL x=q[i].x;
		switch(q[i].op){
			case 1: t.modify({0,x},q[i].l,q[i].r,1,1,n); break;
			case 2: t.modify({0,x},q[i].l,q[i].r,1,1,n); break;
			case 3: t.modify({1,x},q[i].l,q[i].r,1,1,n); break;
			case 4: printf("%lld\n",t.query(q[i].l,q[i].r,1,1,n).ans); break;
			case 5: printf("%lld\n",t.query(q[i].l,q[i].r,1,1,n).ans); break;
		}
	}
	return 0;
}

其实不需要离线,如果是指针。