[省选联考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 v
或 2 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;
}