hdu 4729 An Easy Problem for Elfness (树链剖分-入边-二分-查区间最值)

题意:

有K的经费,询问从a->b 通过两种途径取得最大流量是多少

途径1: 加任意的边

途径2:扩充任意的边

思路:

对于每一个操作来说,如果a<=b那么加a->b的直接连的边必定最合算

如果a>b,那么有两种,第一是扩展原有的边。第二是加一条a->b的边,扩展a->b的边

一开始想复杂了,去搜了下,发现是个很模板的题,但是二分的左边界要处理好。否则会TLE。这题当入边的板子吧!入边入点在编号节点ID时总是感觉有些乱

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <vector>
#include <map>
#define ll long long 
using namespace std;
const int maxn = 200100;
const int inf = 0x3f3f3f3f;
int head[maxn],tot,pos,son[maxn];
int top[maxn],fp[maxn],fa[maxn],dep[maxn],siz[maxn],id[maxn];
int val[maxn];
int n;
struct Edge
{
    int to,next,w;
} edge[maxn<<1];

void init()
{
    tot = 0,pos = 1;
    memset(head,-1,sizeof(head));
    memset(son,-1,sizeof(son));
    memset(siz,0,sizeof(siz)); 
}

void add_edge(int u,int v,int w)
{
    edge[tot].w = w,edge[tot].to = v,edge[tot].next = head[u],head[u] = tot++;
}

void dfs1(int u,int f,int d)
{
    dep[u] = d;
    fa[u] = f,siz[u] = 1;
    for(int i = head[u]; ~i; i = edge[i].next)
    {
        int v = edge[i].to;
        if(v != f)
        {
            ///把边权赋值给下面的点
            val[v] = edge[i].w;
            dfs1(v,u,d+1);
            siz[u] += siz[v];
            if(  siz[v] > siz[son[u]])
                son[u] = v;
        }
    }
}

void getpos(int u,int sp)
{
    top[u] = sp;
    id[u] = pos++;
    ///id是点的编号,fp[id[u]]是一个对于点与自身编号的映射
    fp[id[u]] = u;
    if(son[u] == -1)return ;
    getpos(son[u],sp);
    for(int i = head[u]; ~i ; i = edge[i].next)
    {
        int v = edge[i].to;
        if(v != son[u] && v != fa[u])
            getpos(v,v);
    }
}

struct node
{
    int l,r,mid;
    ll minn;
} tree[maxn << 2];
void push_up(int i)
{
    tree[i].minn = min(tree[i<<1].minn,tree[i<<1|1].minn);
}
void build(int i,int l,int r)
{
    tree[i].l = l,tree[i].r = r;
    tree[i].minn = inf;
    tree[i].mid=(l+r) >>1;
    if(l == r)
    {
        tree[i].minn = val[fp[l]];
        return;
    }
    build(i<<1,l,tree[i].mid);
    build(i<<1|1,tree[i].mid+1,r);
    push_up(i);
}

void update(int i,int k,int val)
{
    if(tree[i].l == k && tree[i].r == k)
    {
        tree[i].minn = val;
        return;
    }
    int mid = tree[i].mid;
    if(k <= mid) update(i<<1,k,val);
    else update(i<<1|1,mid,val);
    push_up(i);
}
ll query(int i,int l,int r)
{
    if(tree[i].l >= l && tree[i].r <= r)
        return tree[i].minn;
    int mid = tree[i].mid;
    if(r <= mid)
        return query(i<<1,l,r);
    else if(l > mid)
        return query(i<<1|1,l,r);
    else
    {
        return min(query(i<<1,l,mid),query(i<<1|1,mid+1,r));
    }
}
///找一个最小的权值作为限制
ll find_flow(int u,int v)
{
    int tpu = top[u],tpv = top[v];
    ll tmp = inf;
    while(tpu != tpv)
    {
        ///先走重链深度大的那条
        if(dep[tpu] < dep[tpv])
        {
            swap(tpu,tpv),swap(u,v);
        }
        tmp = min(tmp,query(1,id[tpu],id[u]));
        u = fa[tpu],tpu = top[u];
    }
    if(u == v) return tmp;
    if(dep[u] > dep[v]) swap(u,v);
    ///此处入点与入边不同!; 入边则为查找id[son[u]] ,id [v] 
    ///入点则为查找id[u],id[v].
    ///原因在于点权的赋值方法不同!
    return min(tmp,query(1,id[son[u]],id[v]));
}
int allnum = 0;
///查找如今可以扩展的总流量是否可以满足使得小于MID的所有边都扩充到mid.
bool can_do(int i,int l,int r,int mid)
{
    if(tree[i].l >= l && tree[i].r <= r && tree[i].minn >= mid)
    {
        return true;
    }
    if(tree[i].l == tree[i].r)
    {
        if(tree[i].minn >= mid)
            return true;
        allnum -= (mid-tree[i].minn);
        if(allnum>= 0) return true;
        return false;
    }
    if(r <= tree[i].mid)
        return can_do(i<<1,l,r,mid);
    else if(l > tree[i].mid)
        return can_do(i<<1|1,l,r,mid);
    else
        return can_do(i<<1,l,tree[i].mid,mid)&&can_do(i<<1|1,tree[i].mid+1,r,mid);
}

bool find_flag(int u,int v,int mid)
{
    int tpu = top[u],tpv = top[v];
    while(tpu != tpv)
    {
        if(dep[tpu] < dep[tpv])
        {
            swap(tpu,tpv),swap(u,v);
        }
        if(!can_do(1,id[tpu],id[u],mid))
            return false;
        u = fa[tpu],tpu = top[u];
    }
    if(u == v) return true;
    if(dep[u] > dep[v]) swap(u,v);
    return can_do(1,id[son[u]],id[v],mid);
}

int a[maxn];


int main()
{
    int T,cas = 1;
    int x,y,k,a,b;
    int m,u,v,w;
   scanf("%d",&T);
    while(T--)
    {
        init();
        scanf("%d%d",&n,&m);
        for(int i =1; i < n; i++)
        {
            scanf("%d%d%d",&u,&v,&w);
            add_edge(u,v,w);
            add_edge(v,u,w);
        }
        dfs1(1,0,0);
        getpos(1,1);
        build( 1,1,pos );

        printf("Case #%d:\n",cas++);
        for(int i = 1;i <= m;i++)
        {
            scanf("%d%d%d%d%d",&x,&y,&k,&a,&b);
            ll flow = find_flow(x,y);
            if(k < min(a,b))
            {
                printf("%lld\n",flow);
            }
            else if(a <= b)
            {
                printf("%lld\n",flow+(ll)k/a);
            }
            else
            {
                ll ans = flow;
                if(k > a)
                    ans += (k-a)/b+1;
                int l = ans,r = 10000;
                while(l <= r)
                {
                    int mid = (l+r)>>1;
                    allnum = k/b;
                    if(find_flag(x,y,mid))
                    {
                        ans = mid,l = mid + 1;
                    }
                    else
                        r = mid - 1;
                }
                printf("%lld\n",ans);
            }
        }
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值