[BZOJ1283]序列-线性规划-最小费用流

本文介绍了一种利用网络流解决特定线性规划问题的方法,通过建立适当的模型将子序列选择问题转换为网络流问题,进而求解最大子序列和。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

序列

Description

给出一个长度为N的正整数序列Ci,求一个子序列,使得原序列中任意长度为M的子串中被选出的元素不超过K(K,M<=100) 个,并且选出的元素之和最大。

Input

第1行三个数N,m,k。 接下来N行,每行一个字符串表示Ci。

Output

最大和。

Sample Input

10 5 3
4 4 4 6 6 6 6 6 4 4

Sample Output

30

HINT

20%的数据:n<=10。
100%的数据:N<=1000,k,m<=100。Ci<=20000。

Source

By YM


线性规划的网络流建模……
太久没复习已经忘了……


思路:
考虑把题目的限制列出来。

xixi代表ii号元素是否被选出。
那么有

{i=1mxiki=2m+1xiki=3m+2xiki=nm+1nxik

可以发现,网络流的流量守恒性质可以帮助解决等式的问题,只需要把每个点当成一个等式即可。
然而咱们上面的式子都是小于等于的关系。
为了把小于等于关系转化为相等关系,对每个等式补充变量yiyi,满足yi0yi≥0,且:

i=1mxi+y1=ki=2m+1xi+y2=ki=3m+2xi+y3=ki=nm+1nxi+ynm+1=k{∑i=1mxi+y1=k∑i=2m+1xi+y2=k∑i=3m+2xi+y3=k⋯∑i=n−m+1nxi+yn−m+1=k

考虑到在网络流建模中,一条边代表一个变量,而这决定了它只能连接两个节点。
也就是说,一个变量只能在两个等式中出现,且一次系数为正,一次系数为负。
这样,从当前变量系数为正的等式向当前变量系数为负的等式连出的边即可代表当前节点~
然而上面的式子并不满足每个变量出现两次。
于是考虑差分:

i=1mxi+y1k=0xm+1x1+y2y1=0xm+2x2+y3y2=0xnxnm+ynm+1ynm=0i=nm+1nxiynm+1+k=0{∑i=1mxi+y1−k=0xm+1−x1+y2−y1=0xm+2−x2+y3−y2=0⋯xn−xn−m+yn−m+1−yn−m=0−∑i=n−m+1nxi−yn−m+1+k=0

于是满足了使用网络流的条件!

于是设立nm+2n−m+2个节点代表每一条等式,对于每个变量xxy,从它系数为正的等式所代表的节点向它系数为负的等式所代表的节点连出等于流量其取值上限的边(如xixi就是11,而yiinfinf),同时配上相应费用(如xixi就是对应位置的值,而yiyi00)。

对于特殊的第一个等式,源点向1号节点连流量为kk,费用为0的边。
对于特殊的最后一个等式,nm+2n−m+2号点向汇点连流量为kk,费用为0的边。

然后问题就解决了!

#include<bits/stdc++.h>
using namespace std;

inline int read()
{
    int x=0;char ch=getchar();
    while(ch<'0' || '9'<ch)ch=getchar();
    while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
    return x;
}

inline bool chkmin(int &a,int b){if(a>b){a=b;return 1;}return 0;}

const int N=1509;
const int M=N*20;
const int Inf=2139062143;

int n,m,k;
int c[N];

namespace flow
{
    int to[M],nxt[M],w[M],cost[M],beg[N],tot=1;
    int s,t,dis[N],fae[N];
    bool inq[N];
    queue<int> q;

    inline void adde(int u,int v,int c,int d)
    {
        to[++tot]=v;
        nxt[tot]=beg[u];
        cost[tot]=d;
        w[tot]=c;
        beg[u]=tot;
    }

    inline void add(int u,int v,int c,int d)
    {
        adde(u,v,c,d);adde(v,u,0,-d);
    }

    inline int nex(int &x)
    {
        x++;if(x>N-5)x=0;
    }

    inline bool spfa()
    {
        memset(dis,127,sizeof(dis));
        while(!q.empty())q.pop();
        dis[s]=0;q.push(s);
        while(!q.empty())
        {
            int u=q.front();q.pop();inq[u]=0;
            for(int i=beg[u],v;i;i=nxt[i])
                if(w[i]>0 && chkmin(dis[v=to[i]],dis[u]+cost[i]))
                {
                    fae[v]=i;
                    if(!inq[v])
                        inq[v]=1,q.push(v);
                }
        }
        return dis[t]!=Inf;
    }

    inline int augment()
    {
        int mflow=Inf;
        for(int i=t;i!=s;i=to[fae[i]^1])
            chkmin(mflow,w[fae[i]]);
        for(int i=t;i!=s;i=to[fae[i]^1])
            w[fae[i]]-=mflow,w[fae[i]^1]+=mflow;
        return mflow*dis[t];
    }

    inline int mcmf()
    {
        int ret=0;
        while(spfa())
            ret+=augment();
        return ret;
    }
}

using namespace flow;

int main()
{
    n=read();m=read();k=read();
    for(int i=1;i<=n;i++)
        c[i]=read();
    s=0,t=n-m+3;
    add(s,1,k,0);add(n-m+2,t,k,0);
    for(int i=1;i<n-m+2;i++)
        add(i,i+1,Inf,0);
    for(int i=1;i<=m;i++)
        add(1,i+1,1,-c[i]);
    for(int i=m+1;i<=n-m;i++)
        add(i-m+1,i+1,1,-c[i]);
    for(int i=n-m+1;i<=n;i++)
        add(i-m+1,n-m+2,1,-c[i]);
    printf("%d\n",-mcmf());
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值