CF903G Yet Another Maxflow Problem

题目链接:戳我

最小割+线段树模拟网络流

5c8617f65777f.jpg
(自己手动画了一个图,有点丑还请见谅)
首先声明一些数组:a[i]表示左边图编号为i的线段的长度,b[i]表示右边图编号为i的线段的长度,sum[i]表示选取左边编号为i的线段的最小代价。

下面我们来看这个题怎么做——

比较神仙。既然题目中都说了"yet another maxflow problem",那肯定不是用最大流来写的。我们考虑转化,转化成最小割。(你问我为什么?我我我。。。我也不太清楚,就当做一个套路吧,而且网络流二分图中的最大流不也很有一部分题目是转化成最小割来写的嘛qwqwq)

转化了之后我们就知道答案肯定是\(dis_{(A_i,A_i+1)}+dis_{(B_i,B_i+1)}+\sum dis_{(p,q)},p\le i,q\ge j\)

我们可以考虑从小到大遍历一遍A,(设现在遍历到的点为u)然后枚举它的出边所指向的v。对于(u,v)这条边,显然它的贡献只产生于\(i\le u,j\ge v\)的边里面。又因为左边遍历的时候已经保证顺序了,所以我们只需要在维护\(b\)值的区间里加上它的值作为贡献即可。

然后我们重新构建一次线段树。。。查询全局最小值。每次修改就是简单的单点修改。

然后。。就没有然后了。。。。就做完了。

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define MAXN 200010
using namespace std;
int n,m,q,edge_number;
int head[MAXN];
long long a[MAXN],b[MAXN],sum[MAXN];
struct Node{int l,r;long long minn,tag;}t[MAXN<<2];
struct Edge{int nxt,to;long long dis;}edge[MAXN<<1];
inline void add(int from,int to,long long dis)
{
    edge[++edge_number].nxt=head[from];
    edge[edge_number].to=to;
    edge[edge_number].dis=dis;
    head[from]=edge_number;
}
inline int ls(int x){return x<<1;}
inline int rs(int x){return x<<1|1;}
inline void push_up(int x){t[x].minn=min(t[ls(x)].minn,t[rs(x)].minn);}
inline void solve(int x,long long k)
{
    t[x].tag+=k;
    t[x].minn+=k;
}
inline void push_down(int x)
{
    if(t[x].tag)
    {
        solve(ls(x),t[x].tag);
        solve(rs(x),t[x].tag);
        t[x].tag=0;
    }
}
inline void build(int x,int l,int r,int op)
{
    t[x].l=l,t[x].r=r;
    if(l==r) 
    {
        if(op==0) t[x].minn=b[l];
        else t[x].minn=sum[l];
        return;
    }
    int mid=(l+r)>>1;
    build(ls(x),l,mid,op);
    build(rs(x),mid+1,r,op);
    push_up(x);
}
inline void build2(int x,int l,int r)
{
    t[x].l=l,t[x].r=r;
    if(l==r) {t[x].minn=sum[l];return;}
    int mid=(l+r)>>1;
    build2(ls(x),l,mid);
    build2(rs(x),mid+1,r);
    push_up(x);
}
inline void update(int x,int ll,int rr,long long k)
{
    int l=t[x].l,r=t[x].r;
    if(ll<=l&&r<=rr) {solve(x,k);return;}
    int mid=(l+r)>>1;
    push_down(x);
    if(ll<=mid) update(ls(x),ll,rr,k);
    if(mid<rr) update(rs(x),ll,rr,k);
    push_up(x);
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("ce.in","r",stdin);
    #endif
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1;i<n;i++) scanf("%I64d%I64d",&a[i],&b[i]);
    for(int i=1;i<=m;i++)
    {
        int u,v;
        long long w;
        scanf("%d%d%I64d",&u,&v,&w);
        add(u,v,w);
    }
    build(1,0,n-1,0);
    for(int i=1;i<=n;i++)
    {
        for(int j=head[i];j;j=edge[j].nxt)
        {
            int v=edge[j].to;
            update(1,0,v-1,edge[j].dis);
        }
        sum[i]=a[i]+t[1].minn;
    }
    //for(int i=1;i<=n;i++) printf("sum[%d]=%I64d\n",i,sum[i]);
    memset(t,0,sizeof(t));
    build(1,1,n,1);
    printf("%I64d\n",t[1].minn);
    for(int i=1;i<=q;i++)
    {
        int u;
        long long k;
        scanf("%d%I64d",&u,&k);
        update(1,u,u,k-a[u]);
        a[u]=k;
        printf("%I64d\n",t[1].minn);
    }
    return 0;
}

转载于:https://www.cnblogs.com/fengxunling/p/10510165.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值