[BNUOJ]Idol Master 费用流

题目描述

何老师沉迷偶像大师无法自拔,现在他拥有n张偶像卡,每张卡有一个能力值,为了准备下一场对战,需要从拥有的偶像卡中选出一些使得它们的能力值之和最大,并且在任意连续的k张卡中,至少要选出a张卡,但不能选超过b张卡。

解法

先把条件按照线性规划的形式写出来:
aki=1xib
ak+1i=2xib
ak+2i=3xib
……
ani=nk+1xib
使得
maxni=1xici

将限制的每条式子补上一个变量使得不等式变成等式
y1+sumki=1xi=b 其中 y1<=ba
添加第0条式子和第n+1条式子0=0
对相邻的两项做差
第0条式子:
y1+ki=1xi=b
第1到n-k+1条式子:
yi+1yi+xi+kxi=0
第n-k+2条式子:
ynni=nk+1xi=b
每个变量在式子里面都出现了两次,一次正一次负
把每个变量看成一条边,系数为1表示出边,系数为-1表示入边
xi 的费用为 ci yi 的费用为 0 <script type="math/tex" id="MathJax-Element-140">0</script>
多余的常数项是从源点/汇点流出来的流量
建图跑最大费用最大流

#include <bits/stdc++.h>
#define INF (1<<29)
#define N 605 
#define M 200050
using namespace std;
typedef long long LL;

int head[N],cnt=1,tot,S,T;
int p[N],L[N],a,b,c[N],d;
LL dis[N];
int n,m,k;
LL ans;

inline int rd() {
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

struct Edge{ int a,b,v,cost,next; }e[M],id;
inline void add(int a,int b,int v,int cost) {
    if (!a || !b) return ; 
    e[++cnt] = (Edge){ a,b,v,cost,head[a] }, head[a] = cnt;
    e[++cnt] = (Edge){ b,a,0,-cost,head[b] },head[b] = cnt;
}

#define cp e[i].v  
#define B e[i].b  

bool SPFA() {  
    bool flag = false;
    for (int i=1;i<=tot;i++) p[i] = 0, dis[i] = -(1LL<<62);
    dis[S] = 0;
    queue<int> q; q.push(S);  
    while (!q.empty()) {  
        int u = q.front(); q.pop();  
        if (u == T) flag = true;
        for (int i=head[u];i;i=e[i].next)  
            if (cp > 0 && dis[u] + e[i].cost > dis[B]) {  
                dis[B] = dis[u] + e[i].cost;  
                p[B] = i;  
                q.push(B);  
            }   
    }  
    return flag;  
}  

void mcf() {  
    int g = p[T] , flow = INF;  
    while (g) {
        flow = min(flow , e[g].v);  
        g = p[ e[g].a ];  
    }
    g = p[T];  
    while (g) {  
        e[g  ].v -= flow;  
        e[g^1].v += flow;  
        ans += 1LL * e[g].cost * flow;  
        g = p[ e[g].a ];  
    }
}
void solve() {
    for (int i=0;i<=tot;i++) head[i] = 0;
    for (int i=0;i<=cnt;i++) e[i] = id;
    cnt = 1, tot = 0;

    n = rd(), k = rd(), a = rd(), b = rd(), d = n-k+1;
    for (int _=1;_<=n;_++) c[_] = rd();

    S = ++tot, T = ++tot;
    for (int _=0;_<=n;_++) L[_] = ++tot;

    add(S, L[0], b, 0), add(L[d], T, b, 0);

    //x[i]

    for (int i=1;i<=n;i++) {
        int p1 = max(i-k, 0), p2 = min(i, d);
        add(L[p1], L[p2], 1, c[i]);
    }

    //y[i]
    for (int i=0;i<d;i++) add(L[i], L[i+1], b-a, 0);

    ans = 0;
    while (SPFA()) mcf();
    printf("%lld\n",ans);
}

int main() {
    for (int T=rd();T;T--) solve();
    return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值