王子 [费用流]

本文介绍了一种利用网络流算法解决染色问题的方法。给定长度为n的格子,通过染色策略使得连续k个格子中至少包含p个黑色和q个白色,以获得最高分数。文章详细阐述了将此问题转化为网络流问题的步骤,并提供了一个具体的代码实现。

题意

给定$n,k,p,q$,现在要给一段长度为n的格子染黑白,第$i$个格子染成黑色得到$a_i$分,白色$b_i$分

要求连续的$k$个格子中要有至少$p$个黑的,$q$和白的

求最大得分

思路

一眼是没想法的......瞄了一眼题解看到“网络流”三个大字,就会了

还是要多想一些方法,多试试,因为这种题看着不是dp就是什么鬼畜的转化,那转化模型肯定先看是不是网络流

考虑序列模型的经典网络流转化方法

本题中的核心限制是对于每一段$k$区间的颜色数限制

可以看到实际上这个$p,q$就是限制黑色格子的数量是$[p,k-q]$

同时这个限制对于每一个格子而言,这个格子参与$k$个区间的限制

我们考虑这样一个方法:

首先把所有的都涂成白色,然后用跨越一个$k$区间的一点流量来代表变成一个黑格子带来的影响,其他的从$i$到$i+1$的代表一个白格子

那么这样我们把白格子边设成流量上限为$k-q-p$上限,费用为0,代表额外的白变黑的数量上限

黑格子边显然容量为1,费用为对应的格子的$b_i-a_i$

一些细节注意:

首先这个跨越的黑格子边,从$i$出发,终点在$k+i$不是在$k+i-1$

然后注意最开始的$k$个点,没有前驱去到达它们,那么建立一个辅助节点$aux$,从$s->aux$连一条容量为$k-q$的(限制一下),再从$aux$连向$1$到$k$号点

实现上可以把费用流的$ans$初值设为所有白格子分数的和,然后用上面定义的费用直接算就可以了

费用流推荐使用spfa魔改版zkw多路增广,不过本题中单路多路其实差距不大,$EK$还可能更快(因为图的形态问题)

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cassert>
#include<queue>
#define ll long long
using namespace std;
inline ll read(){
    ll re=0,flag=1;char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-') flag=-1;
        ch=getchar();
    }
    while(isdigit(ch)) re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
    return re*flag;
}
ll n,m,l,r,cnte=1,first[1010],dis[1010],vis[1010],ans=0,maxf=0;
struct edge{
    ll to,next,w,cap;
}a[20010];
inline void add(ll u,ll v,ll w,ll cap){
    a[++cnte]=(edge){v,first[u],w,cap};first[u]=cnte;
    a[++cnte]=(edge){u,first[v],-w,0,};first[v]=cnte;
}
queue<ll>q;
bool spfa(ll s,ll t){
    ll i,u,v;
    for(i=s;i<=t+1;i++) dis[i]=-111111111111111ll,vis[i]=0;
    vis[t]=1;dis[t]=0;q.push(t);
    while(!q.empty()){
        u=q.front();q.pop();vis[u]=0;
        for(i=first[u];~i;i=a[i].next){
            v=a[i].to;
            if(!a[i^1].cap) continue;
            if(dis[v]!=-111111111111111ll&&(dis[v]>=dis[u]+a[i^1].w)) continue;
            dis[v]=dis[u]+a[i^1].w;
            if(!vis[v]){
                vis[v]=1;
                q.push(v);
            }
        }
    }
    return dis[s]!=-111111111111111ll;
}
ll dfs(ll u,ll t,ll lim){
    if(u==t||!lim) return lim;
    ll i,v,f,flow=0;vis[u]=1;
    for(i=first[u];~i;i=a[i].next){
        v=a[i].to;if(vis[v]) continue;
        if(dis[v]==dis[u]-a[i].w&&(f=dfs(v,t,min(lim,a[i].cap)))){
            a[i].cap-=f;
            a[i^1].cap+=f;
            flow+=f;
            lim-=f;
            ans+=f*a[i].w;
            if(!lim){vis[u]=0;return flow;}
        }
    }
    dis[u]=-111111111111111ll;
    return flow;
}
void mcmf(ll s,ll t){
    while(spfa(s,t)){
        memset(vis,0,sizeof(vis));
        vis[t]=1;
        while(vis[t]){
            memset(vis,0,sizeof(vis));
            maxf+=dfs(s,t,1e9);
        }
    }
}
ll x[1010],y[1010];
int main(){
    memset(first,-1,sizeof(first));
    n=read();m=read();l=read();r=m-read();ll i;
    for(i=1;i<=n;i++) x[i]=read(),y[i]=read(),ans+=y[i];
    add(0,n+2,0,r);
    for(i=1;i<=m;i++) add(n+2,i,0,1);
    for(i=1;i<=n;i++){
        add(i,i+1,0,r-l);
        add(i,((i<=n-m)?i+m:n+1),x[i]-y[i],1);
    }
    mcmf(0,n+1);
    cout<<ans<<'\n';
}

转载于:https://www.cnblogs.com/dedicatus545/p/10023234.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值