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

序列

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


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


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

xi x i 代表 i i 号元素是否被选出。
那么有

i=1mxiki=2m+1xiki=3m+2xiki=nm+1nxik { ∑ i = 1 m x i ≤ k ∑ i = 2 m + 1 x i ≤ k ∑ i = 3 m + 2 x i ≤ k ⋯ ∑ i = n − m + 1 n x i ≤ k

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

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

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

i=1mxi+y1k=0xm+1x1+y2y1=0xm+2x2+y3y2=0xnxnm+ynm+1ynm=0i=nm+1nxiynm+1+k=0 { ∑ i = 1 m x i + y 1 − k = 0 x m + 1 − x 1 + y 2 − y 1 = 0 x m + 2 − x 2 + y 3 − y 2 = 0 ⋯ x n − x n − m + y n − m + 1 − y n − m = 0 − ∑ i = n − m + 1 n x i − y n − m + 1 + k = 0

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

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

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

然后问题就解决了!

#include
using namespace std;

inline int read()
{
    int x=0;char ch=getchar();
    while(ch<'0' || '9'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;i2;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;
}

你可能感兴趣的:(最小费用流,【Min_Cost,Max_Flow】,线性规划【Linear,Programming】)