有一棵
n
n
个点的树。 的父亲为
⌊i/2⌋
⌊
i
/
2
⌋
。
(i>=2)
(
i
>=
2
)
每个点有一个承载上限,给出
m
m
个点的位置。
输出前 个点
(1<=i<=m)
(
1
<=
i
<=
m
)
移动到树上的合法位置(不超过承载上限)的最小代价。
n,m<=3∗105
n
,
m
<=
3
∗
10
5
考虑暴力。
S
S
连初始位置容量为初始点个数费用0,每个点连 ,容量为点的承载上限费用为0.
中间连树边,容量
INF
I
N
F
,费用
1.
1.
最小费用最大流求出答案。
题目中给出的树的性质——完全二叉树。
树高最多
logn
l
o
g
n
优化网络流。暴力的费用流每次增广时找到一个最短路径,把点走到路径,其中如果走了反悔边,但原来放置的点位置上还存在的点。因为树高只有
log
l
o
g
。我们可以预处理出每个点走到其子树中的合法位置最短的路径和点的位置。
因为是树可以
DP
D
P
来求出。然后模拟费用流的增广路过程即可。
代码中的
rev
r
e
v
数组为从
fa(i)
f
a
(
i
)
走到
i
i
有多少剩余的负权边。就相当于网络流中的反向弧的流量。
#include<bits/stdc++.h>
using namespace std;
const int MAXN=8e5+5;
const int INF=1e9;
struct data{
int dis,id;
}sp[MAXN];
int s[MAXN],t[MAXN],rev[MAXN],n,m;//rev(i) 从fa i走到i 的边负数的容量
void modify(int x){
if(t[x]>0)sp[x].dis=0,sp[x].id=x;
else sp[x].dis=INF,sp[x].id=0;
int lson=x<<1,rson=x<<1|1;
if(lson<=n){
int p=rev[lson]<=0?1:-1;
if(sp[lson].dis+p<sp[x].dis)sp[x].dis=sp[lson].dis+p,sp[x].id=sp[lson].id;
}
if(rson<=n){
int p=rev[rson]<=0?1:-1;
if(sp[rson].dis+p<sp[x].dis)sp[x].dis=sp[rson].dis+p,sp[x].id=sp[rson].id;
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&t[i]);
for(int i=1;i<=m;i++)scanf("%d",&s[i]);
for(int i=n;i>=1;i--)modify(i);
// for(int i=1;i<=n;i++)cout<<sp[i].dis<<" "<<sp[i].id<<endl;
int ans=0;
for(int i=1;i<=m;i++){
int p=s[i],t1=sp[p].dis,t2=0,pos=sp[p].id;
t2+=rev[p]<0?-1:1;p>>=1;
while(p){
if(t2+sp[p].dis<t1){
t1=t2+sp[p].dis;
pos=sp[p].id;
}
t2+=rev[p]<0?-1:1;p>>=1;
}
ans+=t1;
printf("%d\n",ans);
t[pos]--;
int x=s[i],y=pos,lca;
// cout<<x<<y<<endl;
while(x!=y){
if(x<y)y>>=1;
else x>>=1;
}
lca=x;
x=s[i],y=pos;
while(x!=lca)rev[x]++,modify(x),x>>=1;
while(y!=lca)rev[y]--,modify(y),y>>=1;
while(lca)modify(lca),lca>>=1;
}
return 0;
}
/*
5 4
0 0 4 1 1
2 4 5 2
*/