[省选联考2023] 人员调度

[省选联考 2023] 人员调度

题目背景

滥用本题评测将封号

题目描述

众所周知,一个公司的 \(n\) 个部门可以组织成一个树形结构。形式化地,假设这些部门依次编号为 \(1, \ldots, n\),那么除了 \(1\) 号部门以外,第 \(i \in [2, n]\) 个部门有且仅有一个上级部门 \(p_i \in [1, i - 1]\)。这样,这家公司的 \(n\) 个部门可以视为一个以 \(1\) 为根的树。如果 \(i\)\(j\) 子树中的点,那么称部门 \(i\) 是部门 \(j\) 的子部门。

该公司初始时有 \(k\) 名优秀员工,编号依次为 \(1 \ldots k\)。第 \(i\) 名优秀员工初始时在第 \(x_i\) 个部门工作,并且其有一个能力值 \(v_i > 0\)

为了最大化公司的运作效率,公司老板 0/\/\G 决定进行一些人员调动。具体来说,可以将编号为 \(i\) 的优秀员工调动到 \(x_i\) 的一个子部门,或者不调度(此时该员工在 \(x_i\) 部门)。随后,优秀员工们会在其所在的部门竞选部门领导——能力值最高者将担任这一职位,并给公司带来等同于其能力值的贡献。如果一个部门一个优秀员工也没有,那么就无法选出部门领导,从而对公司的贡献将是 \(0\)。此时,公司的业绩被定义为公司各部门的贡献之和。

公司老板 0/\/\G 自然想知道,该如何进行人员调动,使公司的业绩最大?

这当然难不倒他,然而,公司优秀员工的数量也会发生变化;具体来说,会依次发生 \(m\) 个事件,每个事件形如:

  • 1 x v:先令 \(k = k + 1\),然后新增一位编号为 \(k\)、初始部门为 \(x\)、能力值为 \(v\) 的优秀员工;
  • 2 id:编号为 \(\mathit{id}\) 的优秀员工将被辞退。

公司老板 0/\/\G 希望你能在最开始和每个事件发生后,告诉他公司的业绩最大可能是多少?

注意,每次人员调动都是独立的,也就是每次计算公司的最大可能业绩时,每个优秀员工都会回到其所在的初始部门。

输入格式

输入的第一行包含一个正整数 \(\mathit{sid}\),表示该测试点对应的数据范围以及特殊性质,详见后表;

输入的第二行包含三个整数 \(n, k, m\),分别表示部门数,初始优秀员工数和事件数。

输入的第三行包含 \(n - 1\) 个正整数 \(p_2, \ldots, p_n\),表示每个部门的上级部门。

接下来 \(k\) 行,每行包含两个正整数 \(x_i, v_i\),表示优秀员工的初始部门和能力值。

接下来 \(m\) 行,每行形如 1 x v2 id 表示一次事件。

输出格式

输出一行包含 \(m + 1\) 个由单个空格隔开的非负整数,依次表示最开始和每个事件发生后,公司的业绩可能的最大值。

样例 #1

样例输入 #1

1
3 2 1
1 1
2 1
1 3
1 2 2

样例输出 #1

4 5

样例 #2

样例输入 #2

见附件中的 transfer/transfer2.in

样例输出 #2

见附件中的 transfer/transfer2.ans

样例 #3

样例输入 #3

见附件中的 transfer/transfer3.in

样例输出 #3

见附件中的 transfer/transfer3.ans

样例 #4

样例输入 #4

见附件中的 transfer/transfer4.in

样例输出 #4

见附件中的 transfer/transfer4.ans

样例 #5

样例输入 #5

见附件中的 transfer/transfer5.in

样例输出 #5

见附件中的 transfer/transfer5.ans

样例 #6

样例输入 #6

见附件中的 transfer/transfer6.in

样例输出 #6

见附件中的 transfer/transfer6.ans

样例 #7

样例输入 #7

见附件中的 transfer/transfer7.in

样例输出 #7

见附件中的 transfer/transfer7.ans

样例 #8

样例输入 #8

见附件中的 transfer/transfer8.in

样例输出 #8

见附件中的 transfer/transfer8.ans

样例 #9

样例输入 #9

见附件中的 transfer/transfer9.in

样例输出 #9

见附件中的 transfer/transfer9.ans

样例 #10

样例输入 #10

见附件中的 transfer/transfer10.in

样例输出 #10

见附件中的 transfer/transfer10.ans

样例 #11

样例输入 #11

见附件中的 transfer/transfer11.in

样例输出 #11

见附件中的 transfer/transfer11.ans

样例 #12

样例输入 #12

见附件中的 transfer/transfer12.in

样例输出 #12

见附件中的 transfer/transfer12.ans

样例 #13

样例输入 #13

见附件中的 transfer/transfer13.in

样例输出 #13

见附件中的 transfer/transfer13.ans

样例 #14

样例输入 #14

见附件中的 transfer/transfer14.in

样例输出 #14

见附件中的 transfer/transfer14.ans

样例 #15

样例输入 #15

见附件中的 transfer/transfer15.in

样例输出 #15

见附件中的 transfer/transfer15.ans

提示

【数据范围】

对于所有的数据,保证:\(1 \le \mathit{sid} \le 15\)\(1 \le n, k \le 10^5\)\(0 \le m \le 10^5\)\(1 \le p_i < i\)\(1 \le x_i, x \le n\)\(1 \le v_i, v \le 10^5\)

对于事件 2,保证:\(1 \le \mathit{id} \le k\) 且编号为 \(\mathit{id}\) 的员工在此事件发生时仍在工作。

测试点编号 \(\mathit{sid}\) \(n \le\) \(k \le\) \(m \le\) 特殊性质
1 \(1\) \(6\) \(6\) \(6\)
2, 3 \(2\) \(9\) \(6\) \(6\)
4, 5 \(3\) \(16\) \(66\) \(66\)
6 ~ 8 \(4\) \(66\) \(66\) \(0\)
9 ~ 11 \(5\) \(2,333\) \(2,333\) \(0\)
12 ~ 14 \(6\) \(10^5\) \(10^5\) \(0\) B
15 ~ 18 \(7\) \(10^5\) \(10^5\) \(0\)
19 ~ 21 \(8\) \(2,333\) \(2,333\) \(2,333\) A
22 ~ 24 \(9\) \(10^5\) \(10^5\) \(10^5\) AB
25 ~ 28 \(10\) \(10^5\) \(10^5\) \(10^5\) A
29 ~ 31 \(11\) \(2,333\) \(2,333\) \(2,333\)
32 ~ 34 \(12\) \(10^5\) \(10^5\) \(10^5\) C
35 ~ 38 \(13\) \(10^5\) \(10^5\) \(10^5\) B
39 ~ 44 \(14\) \(66,666\) \(66,666\) \(66,666\)
45 ~ 50 \(15\) \(10^5\) \(10^5\) \(10^5\)

特殊性质 A:无事件 2;

特殊性质 B:\(p_i = i - 1\)

特殊性质 C:\(v_i = v = 1\)

48pts

容易发现,点 \(x\) 的子树内最多保留 \(sz_x\) 个员工。

每次询问暴力修改,用一个可并堆维护子树内所有的点的集合,然后只保留前 \(sz_x\) 大的即可。

复杂度 \(O(nqlogn)\)

性质A

我们考虑如果在某一个点增加一个员工后,在执行上面的操作,会有什么变化。

设一个点的子树内已经有了 \(v_x\) 个员工,那么当且仅当他到祖先的路径上存在 \(v_x=sz_x\) 的时候,这个点可能会不计入答案。此时找到离他最近的那个满足 \(v_x=sz_x\) 的点,然后找到他的子树内最小的那个员工,考虑如果换掉那个员工,会不会更好。

可以用一个set维护目前某个位置的所有员工,一棵线段树维护 \(sz_x-v_x\),另一颗维护员工的最小值。

满分做法

套上线段树分治就可以了

#include<bits/stdc++.h>
#define sit set<node>::iterator
using namespace std;
typedef long long LL;
const int N=5e5+5,INF=1e9;
int n,k,m,l[N+N],r[N+N],op,in[N],sz[N],son[N],x[N];
int dep[N],fa[N],top[N],none[N],dfn[N],tme,hd[N],e_num,a[N],v[N],cnt;
LL ans;
struct edge{
	int v,nxt;
}e[N<<1];
void add_edge(int u,int v)
{
	e[++e_num]=(edge){v,hd[u]};
	hd[u]=e_num;
}
vector<int>tr[N<<2];
struct caozuo{
	int dep,op,x;
}cz[N*20];
struct node{
	int mn,wh;
	node operator+(const node &n)const{
		if(mn<n.mn)
			return (node){mn,wh};
		return n;
	}
	int operator<(const node &n)const{
		return mn<n.mn;
	}
};
multiset<node>g[N];
struct segment{
	int tag[N<<2]; 
	node tr[N<<2];
	void pushdown(int o,int l,int r)
	{
		int md=l+r>>1;
		tr[o<<1].mn+=tag[o],tr[o<<1|1].mn+=tag[o];
		tag[o<<1]+=tag[o],tag[o<<1|1]+=tag[o];
		tag[o]=0;
	}
	void build(int o,int l,int r,int a[])
	{
		if(l==r)
			return void(tr[o]=(node){a[l],l});
		int md=l+r>>1;
		build(o<<1,l,md,a);
		build(o<<1|1,md+1,r,a);
		tr[o]=tr[o<<1]+tr[o<<1|1];
	}
	void update(int o,int l,int r,int x,int y,int z)
	{
		if(x<=l&&r<=y)
			return tr[o].mn+=z,tag[o]+=z,void();
		pushdown(o,l,r);
		int md=l+r>>1;
		if(md>=x)
			update(o<<1,l,md,x,y,z);
		if(md<y)
			update(o<<1|1,md+1,r,x,y,z);
		tr[o]=tr[o<<1]+tr[o<<1|1];
	}
	void update(int o,int l,int r,int x,int y)
	{
		pushdown(o,l,r);
		if(l==r)
			return tr[o].mn=y,void();
		int md=l+r>>1;
		if(md>=x)
			update(o<<1,l,md,x,y);
		else
			update(o<<1|1,md+1,r,x,y);
		tr[o]=tr[o<<1]+tr[o<<1|1];
	}
	node query(int o,int l,int r,int x,int y)
	{
		if(x<=l&&r<=y)
			return tr[o];
		int md=l+r>>1;
		node ret=(node){INF,0};
		pushdown(o,l,r);
		if(md>=x)
			ret=ret+query(o<<1,l,md,x,y);
		if(md<y)
			ret=ret+query(o<<1|1,md+1,r,x,y);
		return ret;
	}
}s,c;//s维护最大值,c维护有没有满 
int read()
{
	int s=0;
	char ch=getchar();
	while(ch>'9'||ch<'0')
		ch=getchar();
	while(ch<='9'&&ch>='0')
		s=s*10+ch-48,ch=getchar();
	return s;
}
void dfs(int x,int y)
{
	sz[x]=1,dep[x]=dep[y]+1,fa[x]=y;
	for(int i=hd[x];i;i=e[i].nxt) 
	{
		if(e[i].v==y)
			continue;
		dfs(e[i].v,x);
		sz[x]+=sz[e[i].v];
		if(sz[e[i].v]>sz[son[x]])
			son[x]=e[i].v;
	}
}
void sou(int x,int tp)
{
	dfn[in[x]=++tme]=x,a[tme]=sz[x],top[x]=tp;
	if(son[x])
		sou(son[x],tp);
	for(int i=hd[x];i;i=e[i].nxt)
		if(e[i].v^fa[x]&&e[i].v^son[x])
			sou(e[i].v,e[i].v);
}
void update(int o,int l,int r,int x,int y,int z)
{
	if(x<=l&&r<=y)
		return tr[o].push_back(z),void();
	int md=l+r>>1;
	if(md>=x)
		update(o<<1,l,md,x,y,z);
	if(md<y)
		update(o<<1|1,md+1,r,x,y,z);
}
void era(int x,int s)
{
	sit it=g[x].lower_bound((node){v[s],s});
	g[x].erase(it);
}
void add(int u,int dep,int op)
{
	ans+=v[u]*op;
    cz[++cnt]=(caozuo){dep,op,u};
	int k=x[u];
	if(op==1)
		g[k].insert((node){v[u],u});
	else
		era(k,u);
	if(g[k].empty())
    	s.update(1,1,n,in[k],INF);
    else
		s.update(1,1,n,in[k],g[k].begin()->mn);
//	if(cnt==7083)
//		puts("NO!");
    while(k)
    {
    	c.update(1,1,n,in[top[k]],in[k],-op);
    	k=fa[top[k]];
	}
}
void pus(int u,int dep)
{
//	printf("%d %d\n",u,dep);
	int k=x[u];
	while(k)
	{
		node p=c.query(1,1,n,in[top[k]],in[k]);
		if(!p.mn)
		{
//			puts("NO!!!");
			int q=dfn[p.wh];
//			puts("NO!!!!");
			int fk=s.query(1,1,n,in[q],in[q]+sz[q]-1).wh;
//			puts("NO!!!!!");
			if(g[dfn[fk]].begin()->mn>v[u])
				return;
			add(g[dfn[fk]].begin()->wh,dep,-1);
			break;
		}
		k=fa[top[k]];
	}
	add(u,dep,1);
}
void solve(int o,int l,int r,int dep)
{
//	printf("%d %d %d %d %d\n",o,l,r,dep,tr[o].size());
	LL ret=ans;
//	puts("hjhyyds");
	for(int i=0;i<tr[o].size();i++)
		pus(tr[o][i],dep);
	int md=l+r>>1;
//	if(o==3)
//		puts("qzmyyds");
	if(l^r)
		solve(o<<1,l,md,dep+1),solve(o<<1|1,md+1,r,dep+1);
	else
		printf("%lld ",ans);
	while(cnt&&cz[cnt].dep==dep)
	{
//		if(clock()>=CLOCKS_PER_SEC)
//			printf("%d\n",cnt);
		add(cz[cnt].x,dep,-cz[cnt].op);
		--cnt,--cnt;
	}
	ans=ret;
}
int main()
{
//	freopen("transfer9.in","r",stdin) ;
//	freopen("me.out","w",stdout ) ;
	memset(none,0x7f,sizeof(none));
	read(),n=read(),k=read(),m=read();
	for(int i=2,u;i<=n;i++)
		u=read(),add_edge(u,i);
	dfs(1,0);
	sou(1,0);
	for(int i=1;i<=k;i++)
		x[i]=read(),v[i]=read(),l[i]=0,r[i]=m;
	for(int i=1,u;i<=m;i++)
	{
		op=read();
		if(op^2)
		{
			x[++k]=read(),v[k]=read();
			l[k]=i,r[k]=m;
		}
		else
			r[read()]=i-1;
	}
//	printf("%d\n",k);
	s.build(1,1,n,none),c.build(1,1,n,a);
	for(int i=1;i<=k;i++)
	{
		update(1,0,m,l[i],r[i],i);
//		printf("hjh:%d %d\n",l[i],r[i]);
	}
	solve(1,0,m,0);
	return 0;
}
posted @ 2023-04-29 10:45  灰鲭鲨  阅读(62)  评论(0编辑  收藏  举报