Description
鼹鼠们在底下开凿了n个洞,由n-1条隧道连接,对于任意的i>1,第i个洞都会和第i/2(取下整)个洞间有一条隧
道,第i个洞内还有ci个食物能供最多ci只鼹鼠吃。一共有m只鼹鼠,第i只鼹鼠住在第pi个洞内,一天早晨,前k只
鼹鼠醒来了,而后n-k只鼹鼠均在睡觉,前k只鼹鼠就开始觅食,最终他们都会到达某一个洞,使得所有洞的ci均大
于等于该洞内醒着的鼹鼠个数,而且要求鼹鼠行动路径总长度最小。现对于所有的1<=k<=m,输出最小的鼹鼠行动
路径的总长度,保证一定存在某种合法方案。
Input
第一行两个数n,m(1<=n,m<=100000),表示有n个洞,m只鼹鼠。
第二行n个整数ci表示第i个洞的食物数。
第三行m个整数pi表示第i只鼹鼠所在洞pi。
Output
输出一行m个整数,第i个整数表示当k=i时最小的鼹鼠行动路径总长度。
可以得到一个比较显然的最小费用流建图:
原有树边流量+inf,费用1,双向
源点到第k只鼹鼠的位置连边,流量1,费用-k*inf(强制第k次增广时走这条边)
每个点i到汇点连边,流量ci,费用0
每次增广一个单位流量可得到所有答案
考虑优化找最小费用增广路的过程,只需关心增广路在树上的部分
维护树上从根走到每个点的最小费用(深度),当增广导致反向边出现或消失从而使一条向下的边费用改变时,打标记修改整棵子树的深度,同时维护子树内 到汇点的边还没满流的点 的 最小深度
起点是已知的,枚举起点和终点可能的lca,查出最优的终点,增广并修改对应边权,更新起点到终点、lca到根的路径上的信息
标记上下传的具体实现类似于平衡树上的标记
时间复杂度O(nlogn)
#include<cstdio> const int N=100007,inf=0x3f3f3f3f; int n,m; char buf[N*50],*ptr=buf; int _(){ int x=0; while(*ptr<48)++ptr; while(*ptr>47)x=x*10+*ptr++-48; return x; } int ev[N],mx,et[N],ans=0; struct node{ int dep,a,mn; void add(int x){ dep+=x;a+=x;mn+=x; } }tr[N]; void mins(int&a,int b){if(a>b)a=b;} void up(int w){ int l=w<<1,r=l^1; tr[w].mn=ev[w]?tr[w].dep:inf; if(l<=n)mins(tr[w].mn,tr[l].mn); if(r<=n)mins(tr[w].mn,tr[r].mn); } void dn(int w){ if(!tr[w].a)return; int l=w<<1,r=l^1; if(l<=n)tr[l].add(tr[w].a); if(r<=n)tr[r].add(tr[w].a); tr[w].a=0; } void dfs(int w,bool f){ dn(w); if(ev[w]&&tr[w].mn==tr[w].dep){ --ev[w]; }else{ int u=w<<1^(tr[w].mn!=tr[w<<1].mn); dfs(u,1); } up(w); if(f&&!++et[w])tr[w].add(2); } void dns(int w){ if(!w)return; dns(w>>1); dn(w); } void mcf(int x){ dns(x); int mc=inf,mw,c1=0; for(int y=x;y;y>>=1){ int c2=c1+tr[y].mn-tr[y].dep; if(c2<mc){ mc=c2; mw=y; } c1+=et[y]>0?-1:1; } for(int y=x;y!=mw;y>>=1){ up(y); if(!et[y]--)tr[y].add(-2); } dfs(mw,0); for(;mw;mw>>=1)up(mw); ans+=mc; } int main(){ fread(buf,1,sizeof(buf),stdin)[buf]=0; n=_();m=_(); for(int i=1;i<=n;++i)ev[i]=_(); for(int i=1;i<=n;++i)tr[i].dep=tr[i>>1].dep+1; for(int i=n;i;--i)up(i); for(int i=0;i<m;++i){ if(i)putchar(32); mcf(_()); printf("%d",ans); } return 0; }