Bird

有一棵 n n 个点的树。i 的父亲为 i/2 ⌊ i / 2 ⌋ (i>=2) ( i >= 2 )
每个点有一个承载上限,给出 m m 个点的位置。
输出前 i个点 (1<=i<=m) ( 1 <= i <= m ) 移动到树上的合法位置(不超过承载上限)的最小代价。

n,m<=3105 n , m <= 3 ∗ 10 5
考虑暴力。 S S 连初始位置容量为初始点个数费用0,每个点连 T ,容量为点的承载上限费用为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
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值