[CF802C]Heidi and Library (hard)

题意

你有一个容量为\(k\)的书架,每次给定一本书,如果集合中没有这个书就需要花费\(c_i\)的价格将这个书加入集合,可以随时丢掉集合内的书,求完成所有请求的最小花费

题解

为什么我一开始看到这题会想到最小割==
但是每种物品是可以多次对答案造成贡献的,所以显然不能最小割,只能费用流了
那么考虑怎么建图
考虑买入有点困难
可以考虑卖出,卖出的意义就是买入了一个原先有的东西
那么我们把每个要求拆点
拆成两个点\(D_1,D_2\)
然后\(S\to D1\)连流量为1,费用为这天物品的花费的边
\(D_1\to D_2\)连流量为1,费用为0的边表示扔掉这本书
\(D2\to T\)连流量为1,费用为0的边表示今天的物品是否扔掉
\(D_{i,1} \to D_{i+1,1}\)连流量为\(k-1\),费用为0的边表示最多保留\(k-1\)本书,因为明天还要买入一本
\(D_{i-1,1} \to D_{pre_i,2}\)表示已经有这本书了,可以把第\(i\)天出现的这本书卖掉了(\(pre_i\)表示第\(i\)天出现的书上一个出现的时间)
然后最小费用流即可

代码

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
const int M = 205 ;
const int INF = 1e9 ;
using namespace std ;

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

bool exist[M] ;
int n , k , S , T , num = 1 , hea[M] ;
int idx[M] , cst[M] , prep[M] , pos[M] ;
int dis[M] , pre[M] , ans ;
struct E {
    int nxt , to , dis , cst ;
} edge[M * 20] ;
inline void Insert_edge(int from , int to , int dis , int cst) {
    edge[++num].nxt = hea[from] ; edge[num].to = to ;
    edge[num].dis = dis ; edge[num].cst = cst ; hea[from] = num ;
}
inline void add_edge(int u , int v , int w , int c) {
    Insert_edge(u , v , w , c) ;
    Insert_edge(v , u , 0 , -c) ;
}
inline bool Spfa() {
    queue < int > q ; q.push(S) ; pre[T] = -1 ;
    memset(dis , 63 , sizeof(dis)) ; dis[S] = 0 ;
    while(!q.empty()) {
        int u = q.front() ; q.pop() ; exist[u] = false ;
        for(int i = hea[u] ; i ; i = edge[i].nxt) {
            int v = edge[i].to ; 
            if(dis[v] > dis[u] + edge[i].cst && edge[i].dis > 0) {
                pre[v] = i ;
                dis[v] = dis[u] + edge[i].cst ;
                if(!exist[v]) {
                    exist[v] = true ;
                    q.push(v) ;
                }
            }
        }
    }
    return (pre[T] > 0) ;
}
inline void Mcmf() {
    while(Spfa()) {
        int diss = INF ;
        for(int i = T ; i != S ; i = edge[pre[i] ^ 1].to) 
            diss = min(diss , edge[pre[i]].dis) ;
        for(int i = T ; i != S ; i = edge[pre[i] ^ 1].to) 
            edge[pre[i]].dis -= diss , edge[pre[i] ^ 1].dis += diss ;
        ans += dis[T] * diss ;
    }
}
int main() {
    n = read() ; k = read() ; S = 0 ; T = n * 2 + 1 ;
    for(int i = 1 ; i <= n ; i ++) {
        idx[i] = read() ;
        prep[i] = pos[idx[i]] ;
        pos[idx[i]] = i ;
    }
    for(int i = 1 ; i <= n ; i ++) cst[i] = read() ;
    for(int i = 1 ; i <= n ; i ++) {
        add_edge(S , i * 2 - 1 , 1 , cst[idx[i]]) ;
        add_edge(i * 2 - 1 , i * 2 , 1 , 0) ;
        add_edge(i * 2 , T , 1 , 0) ;
        if(i != n) add_edge(i * 2 - 1 , (i + 1) * 2 - 1 , k - 1 , 0) ;
        if(prep[i]) add_edge((i - 1) * 2 - 1 , prep[i] * 2 , 1 , -cst[idx[i]]) ;
    }
    Mcmf() ;
    printf("%d\n",ans) ;
    return 0 ;
}

转载于:https://www.cnblogs.com/beretty/p/10726180.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值