HDU 4729 题解

题目大意

有一棵树,给你边的容量。每次给你s,t,k,a,b,求s到t的最大流。k是总预算,a是加一条边的费用(容量为1),b是扩充一条边的费用。

我的做法

LCA+树套可持久化线段树

建立权值线段树,每个节点保存这个区间内边的数目,这个区间内边容量之和。初始的流量flow就是s到t的最小值。
接下来要分类讨论
1.a<=b:就拿全部预算加边,答案为flow+k/a。
2.a>b:
(1)加一条边,然后扩充这条边,答案为flow+(k-a)/b+1。
(2)把这k/b此机会全部扩充边,答案为扩充后容量最小的边。这里在线段树上二分去寻找答案。

#include<cstdio>
#include<cstring>
#include<list>
using namespace std;
typedef long long ll;
int ls[5000010];
int rs[5000010];
ll num[5000010];
ll sum[5000010];
int cnt;
int root[100010];
void mt(int p)
{
    num[p]=num[ls[p]]+num[rs[p]];
    sum[p]=sum[ls[p]]+sum[rs[p]];
}
int build(int l,int r)
{
    int p=++cnt;
    ls[p]=rs[p]=0;
    num[p]=sum[p]=0;
    if(l==r)
     return p;
    int mid=(l+r)>>1;
    ls[p]=build(l,mid);
    rs[p]=build(mid+1,r);
}
int insert(int p1,int x,int l,int r)
{
    int p2=++cnt;
    ls[p2]=ls[p1];
    rs[p2]=rs[p1];
    num[p2]=num[p1];
    sum[p2]=sum[p1];
    if(l==r)
    {
        num[p2]++;
        sum[p2]+=l;
        return p2;
    }
    int mid=(l+r)>>1;
    if(x<=mid)
     ls[p2]=insert(ls[p1],x,l,mid);
    else
     rs[p2]=insert(rs[p1],x,mid+1,r);
    mt(p2);
    return p2;
}
int find(int p1,int p2,int p3,int l,int r)
{
    if(l==r)
     return l;
    int s=num[ls[p1]]+num[ls[p2]]-2*num[ls[p3]];
    int mid=(l+r)>>1;
    if(s)
     return find(ls[p1],ls[p2],ls[p3],l,mid);
    return find(rs[p1],rs[p2],rs[p3],mid+1,r);
}
ll solve(int p1,int p2,int p3,int k,ll num_,ll sum_,int l,int r)
{
    if(l==r)
     return l-1;
    int mid=(l+r)>>1;
    ll tmp_num=num[ls[p1]]+num[ls[p2]]-2*num[ls[p3]];
    ll tmp_sum=sum[ls[p1]]+sum[ls[p2]]-2*sum[ls[p3]];
    if((num_+tmp_num)*mid-(sum_+tmp_sum)>k)
     return solve(ls[p1],ls[p2],ls[p3],k,num_,sum_,l,mid);
    return solve(rs[p1],rs[p2],rs[p3],k,num_+tmp_num,sum_+tmp_sum,mid+1,r);
}
struct tree
{
    int f;
    int top;
    int w;
    int deep;
    int size;
    int son;
    int msize;
    int v;
    tree()
    {
        v=f=top=w=deep=size=son=msize=0;
    }
};
tree a[100010];
list<pair<int,int> > l[100010];
int cnt2;
void dfs1(int x,int f,int deep)
{
    a[x].v=a[x].f=a[x].top=a[x].w=a[x].deep=a[x].size=a[x].son=a[x].msize=0;
    a[x].f=f;
    a[x].deep=deep;
    a[x].size=1;
    list<pair<int,int> >::iterator p;
    int v;
    for(p=l[x].begin();p!=l[x].end();p++)
     if((v=p->first)!=f)
     {
        dfs1(v,x,deep+1);
        a[x].size+=a[v].size;
        if(a[v].size>a[x].msize)
        {
            a[x].msize=a[v].size;
            a[x].son=v;
            a[x].v=p->second;
        }
     }
}
void dfs2(int x,int top,int d)
{
    a[x].w=++cnt2;
    a[x].top=top;
    root[a[x].w]=insert(root[a[a[x].f].w],d,0,10000);
    if(!a[x].son)
     return;
    dfs2(a[x].son,top,a[x].v);
    int v;
    list<pair<int,int> >::iterator p;
    for(p=l[x].begin();p!=l[x].end();p++)
     if((v=p->first)!=a[x].f&&v!=a[x].son)
      dfs2(v,v,p->second);
}
int getlca(int x,int y)
{
    while(a[x].top!=a[y].top)
     if(a[a[x].top].deep>a[a[y].top].deep)
      x=a[a[x].top].f;
     else
      y=a[a[y].top].f;
    if(a[x].deep<a[y].deep)
     return x;
    return y;
}
int main()
{
    freopen("hdu4729.in","r",stdin);
    freopen("hdu4729.out","w",stdout);
    int t;
    scanf("%d",&t);
    int tt=0;
    while(t--)
    {
        printf("Case #%d:\n",++tt);
        cnt=cnt2=0;
        int n,m;
        int u,v,c;
        int i;
        scanf("%d%d",&n,&m);
        for(i=1;i<=n;i++)
         l[i].clear();
        for(i=1;i<n;i++)
        {
            scanf("%d%d%d",&u,&v,&c);
            l[u].push_back(make_pair(v,c));
            l[v].push_back(make_pair(u,c));
        }
        root[0]=build(0,10000);
        dfs1(1,0,1);
        dfs2(1,1,0);
        for(i=1;i<=m;i++)
        {
            int s,t,k,c,d;
            scanf("%d%d%d%d%d",&s,&t,&k,&c,&d);
            int lca=getlca(s,t);
            ll flow=find(root[a[s].w],root[a[t].w],root[a[lca].w],0,10000);
            if(c<=d)
             printf("%I64d\n",flow+k/c);
            else
            {
                ll ans=0;
                if(k>=c)
                 ans=max(ans,flow+(k-c)/d+1);
                ans=max(ans,solve(root[a[s].w],root[a[t].w],root[a[lca].w],k/d,0,0,0,10000));
                printf("%I64d\n",ans);
            }
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值