BZOJ1283 - 序列

Portal

Description

给出一个长度为\(n(n\leq1000)\)的正整数序列\(\{c_n\}\),从中选出若干个元素,使得原序列中任意连续\(m(m\leq100)\)个数中被选出的元素不超过\(k(k\leq100)\)个,并且选出的元素之和最大。

Solution

容易知道,这个问题是一个线性规划问题:
\[\begin{align*} max \quad & \sum_{i=1}^n c_ix_i\\ s.t. \quad & x_1+x_2+...+x_m+y_1 = k \\ & x_2+x_3+...+x_{m+1}+y_2 = k \\ &... \\ & x_{n-m+1}+x_{n-m+2}+...+x_n+y_{n-m+1} = k \\ & x_i\in\{0,1\},y_i\geq 0 \end{align*}\]\(x_i=1\)表示选中了\(c_i\)这个数。但直接跑这个会TLE╮(╯﹏╰)╭
于是将其差分:
\[\begin{align*} max \quad & \sum_{i=1}^n c_ix_i\\ s.t. \quad & x_1+x_2+...+x_m+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 \\ & x_i\in\{0,1\},y_i\geq 0 \end{align*}\]然后发现这个是可以转化成网络流的。怎么发现的呢?每个变量都至多有一次加一次减,那么可以把每个等式看做一个点,每个变量看做一条边。如果某个变量在等式\(u\)中是减,在等式\(v\)中是加,那么就有边\((u,v)\);如果只在\(u\)中加,那么有\((s,u)\);如果只在\(u\)中减,那么有\((u,t)\)。对于代表\(x_i\)的边,其容量为\(1\),费用为\(c_i\);代表\(y_i\)得便,容量为\(+\infty\),费用为\(0\)。跑一下最大费用最大流就好啦。

Code

//序列
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
inline char gc()
{
    static char now[1<<16],*S,*T;
    if(S==T) {T=(S=now)+fread(now,1,1<<16,stdin); if(S==T) return EOF;}
    return *S++;
}
inline int read()
{
    int x=0; char ch=gc();
    while(ch<'0'||'9'<ch) ch=gc();
    while('0'<=ch&&ch<='9') x=x*10+ch-'0',ch=gc();
    return x;
}
int const N=1e3+10;
int const INF=0x3F3F3F3F;
int n,m,k,a[N];
int s,t;
int h[N],cnt;
struct edge{int v,c,w,nxt;} ed[N*300];
void edAdd(int u,int v,int c,int w)
{
    cnt++; ed[cnt].v=v,ed[cnt].c=c,ed[cnt].w=w,ed[cnt].nxt=h[u],h[u]=cnt;
    cnt++; ed[cnt].v=u,ed[cnt].c=0,ed[cnt].w=-w,ed[cnt].nxt=h[v],h[v]=cnt;
}
int dst[N],pre[N];
queue<int> Q; bool inQ[N];
bool SPFA()
{
    for(int i=1;i<=t;i++) dst[i]=-INF,pre[i]=0;
    dst[s]=0; Q.push(s),inQ[s]=true;
    while(!Q.empty())
    {
        int u=Q.front(); Q.pop(),inQ[u]=false;
        for(int i=h[u];i;i=ed[i].nxt)
        {
            int v=ed[i].v,w=ed[i].w;
            if(ed[i].c&&dst[u]+w>dst[v])
            {
                dst[v]=dst[u]+w,pre[v]=i;
                if(!inQ[v]) Q.push(v),inQ[v]=true;
            }
        }
    }
    return dst[t]>-INF;
}
int maxCost()
{
    int cost=0;
    while(SPFA())
    {
        int fl=INF;
        for(int i=pre[t];i;i=pre[ed[i^1].v]) fl=min(fl,ed[i].c);
        for(int i=pre[t];i;i=pre[ed[i^1].v]) ed[i].c-=fl,ed[i^1].c+=fl;
        cost+=fl*dst[t];
    }
    return cost;
}
int main()
{
    n=read(),m=read(),k=read();
    for(int i=1;i<=n;i++) a[i]=read();
    s=n-m+2,t=n-m+3; cnt=1;
    int t1=n-m+1;
    //针对x建边
    if(m+1<=n-m)
    {
        for(int i=1;i<=m;i++) edAdd(i,t1,1,a[i]);
        for(int i=m+1;i<=n-m;i++) edAdd(i,i-m,1,a[i]);
        for(int i=n-m+1;i<=n;i++) edAdd(s,i-m,1,a[i]);
    }
    else
    {
        for(int i=1;i<=n-m;i++) edAdd(i,t1,1,a[i]);
        for(int i=n-m+1;i<=m;i++) edAdd(s,t1,1,a[i]);
        for(int i=m+1;i<=n;i++) edAdd(s,i-m,1,a[i]);
    }
    //针对y建边
    for(int i=2;i<=n-m;i++) edAdd(i,i-1,INF,0);
    edAdd(1,t1,INF,0); edAdd(s,n-m,INF,0); edAdd(t1,t,k,0);
    //最大费用流
    printf("%d\n",maxCost());
    return 0;
}


转载于:https://www.cnblogs.com/VisJiao/p/BZOJ1283.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值