传送门
解析:
虽然说确实是一个很裸的费用流,但是开始真没往模拟费用流上面想。
然后发现有负环了,打了 D i j k s t r a Dijkstra Dijkstra加势,不知道哪里错了就咕咕咕了。
知道是模拟费用流之后感觉还挺傻逼的。
由于我们确定是一棵完全二叉树了,那么树高就是 Θ ( log n ) \Theta(\log n) Θ(logn)。
这么优秀的结构就没必要用裸的费用流跑了。
我们发现,实际上树边处是不限制流量的。
维护一下每个点向子树内跑到一个空位的最小费用,维护每条树边的当前流量。
然后就能愉快的进行模拟费用流操作了。
代码:
#include<bits/stdc++.h>
#define ll long long
#define re register
#define gc get_char
#define cs const
namespace IO{
inline char get_char(){
static cs int Rlen=1<<20|1;
static char buf[Rlen],*p1,*p2;
return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
}
inline int getint(){
re char c;
while(!isdigit(c=gc()));re int num=c^48;
while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
return num;
}
}
using namespace IO;
using std::cout;
using std::cerr;
cs int N=3e5+5;
int n,m;
int c[N],flow[N],son[N],down[N];
inline void pushup(int u){
son[u]=2;
down[u]=c[u]?0:0x3f3f3f3f;
for(int re i=0,v;i<2;++i){
v=u<<1|i;
if(v<=n){
int cur=down[v]+(flow[v]>=0?1:-1);
if(cur<down[u]){
down[u]=cur;
son[u]=i;
}
}
}
}
inline void augment(int u){
if(c[u]){
--c[u];
}
else {
int v=u<<1|son[u];
++flow[v];
augment(v);
}
pushup(u);
}
ll ans;
signed main(){
n=getint(),m=getint();
for(int re i=1;i<=n;++i)c[i]=getint();
for(int re i=n;i;--i)pushup(i);
while(m--){
int u=getint(),anspos=-1,mn=0x3f3f3f3f,add=0;
for(int re v=u;v;v>>=1){
if(down[v]+add<=mn){
anspos=v;
mn=down[v]+add;
}
add+=flow[v]>0?-1:1;
}
cout<<(ans+=mn)<<" ";
for(int re v=u;v>anspos;v>>=1)--flow[v],pushup(v);
augment(anspos);
for(int re v=anspos;v;v>>=1)pushup(v);
}
return 0;
}