「Usaco2011 Jan」瓶颈

这题,主要是想清楚每个点的过程
对于一个点,他能走完,当且仅当,流向它的权值和小于它流出的权值
那么对于每一个点,总有一个流完的时间点

那么接下来的问题就是,那如果走不完呢,出减入变成负的了咋整
没事,他的儿子总有流完的时候,等到他儿子流完了,他父亲被流入的就少了,也就有了新的流完时间点。
那么怎么得出这个时间点呢?

我们把他和他父亲缩在一起,形成一个新的点,我们考虑这个新点的出入量是什么,因为他的儿子流完了,所以流入应小于流出,所以等于每秒贡献的为他自己被流入的量。
所以,新点的净流出应该为
他的父亲的净流出量减去此儿子的流入量加儿子的流出量
清楚一点
新 点 每 时 间 减 少 的 = 出 父 亲 + 出 流 完 的 儿 子 − 入 儿 子 1 − > n ( 包 括 流 完 的 儿 子 ) − 入 流 完 的 儿 子 的 所 有 流 入 新点每时间减少的=出_{父亲}+出_{流完的儿子}-入_{儿子1->n(包括流完的儿子)}-入_{流完的儿子的所有流入} =+1>n()
流完的儿子流出的当然等于从流完的儿子流向父亲的,是一个东西嘛,所以实际上为
出 父 亲 − 入 儿 子 1 − > n ( 不 包 括 流 完 的 儿 子 ) − 入 流 完 的 儿 子 的 所 有 流 入 出_{父亲} - 入_{儿子1->n(不包括流完的儿子)}-入_{流完的儿子的所有流入} 1>n()
用父子流量和除以新的净流出,就得出了上面提到的新时间点

这样我们就可以不断的按时间节点更新了
用并查集维护父子合并即可
因为询问不一定按照时间顺序, 记得两次排序

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#define INF 2147483647
using namespace std;
const int maxn = 2e5+7;
 
struct node{
    int pos;
    long long t,ans;
}task[maxn];
bool cmp1(node a,node b){
    return a.t<b.t;
}
bool cmp2(node a,node b){
    return a.pos<b.pos;
}
 
int f[maxn],fa[maxn];
long long cow[maxn],flow[maxn],pass[maxn];
int n,m;
priority_queue< pair<long long,int> >q;
 
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
 
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)f[i]=i;
    for(int i=2;i<=n;i++){
        scanf("%d%lld%lld",&fa[i],&cow[i],&flow[i]);
        pass[fa[i]]-=flow[i];
        pass[i]+=flow[i];
    }
    for(int i=1;i<=m;i++){
        scanf("%lld",&task[i].t);
        task[i].pos=i;
    }
    sort(task+1,task+1+m,cmp1);
    for(int i=2;i<=n;i++){
        if(pass[i]>0)q.push(make_pair(-(cow[i]/pass[i]),i));
    }
    int l=1;
    while(!q.empty()&&l<=m){
        while(l<=m&&task[l].t<=-q.top().first){
            task[l].ans=cow[1]-pass[1]*task[l].t,l++;
        }
        if(f[q.top().second]!=q.top().second){q.pop();continue;}
        int f1=q.top().second,f2=find(fa[q.top().second]);q.pop();
        cow[f2]+=cow[f1];pass[f2]+=pass[f1];
        if(pass[f2]>0)q.push(make_pair(-cow[f2]/pass[f2],f2));
        f[f1]=f2;
    }
    sort(task+1,task+1+m,cmp2);
    for(int i=1;i<=m;i++)printf("%lld\n",task[i].ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值