【YBT2022寒假Day6 B】【luogu CF802C】大图书馆 / Heidi and Library (hard)(网络流)

大图书馆 / Heidi and Library (hard)

题目链接:YBT2022寒假Day6 B / luogu CF802C

题目大意

你有一个容量为 k 的桶,然后每天有需求要一个编号的东西,这天结束时会还回来。
然后你可以花一定的钱买入一个编号的东西,或者把东西扔掉,买的东西不能超过容量数,然后问你最小花费。

思路

考虑网络流建图。

首先是每天要买,源点连向点流量 1 1 1 费用为买的花费,然后是提供,每天都要提供,所以点想汇点连流量为 1 1 1费用为 0 0 0
那你会一个提供多个啊,你网络流无法实现啊。

那我们考虑这样,我们肯定每个都买,然后我们弄一个类似卖的操作。即如果之前有这个,那我们就可以走一个卖的边来退钱。

那我们考虑拆点,前面的就源点留到拆出的第一个点,第一个点连流量 1 1 1 费用 0 0 0 点到第二个点,再连到汇点。
然后保留的操作我们就每个点拆出的第一个点顺着连过去,流量是 k − 1 k-1 k1 费用是 0 0 0
(这里是 k − 1 k-1 k1 是因为你当日要买入给今天用的,所以就一定要占一个位置,而且这样的话我们要保留这个种类的话保留到它前一天就可以结束了)

然后就是卖出了,因为上面这句话,所以我们是从这个点的上一个点拆出来的第一个连向这个种类上一次出现的点拆出来的第二个,费用是这次的费用的相反数,流量是 1 1 1

然后跑费用流就可以得出结果了。

代码

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#define INF 0x3f3f3f3f3f3f3f3f

using namespace std;

struct node {
	int x, to, nxt, cst, op;
}e[1000001];
int n, k, c[1001], tot, S, T, lee[2501];
int lst[1001], a[1001], le[2501], KK;

void add(int x, int y, int z, int va) {
	e[++KK] = (node){z, y, le[x], va, KK + 1}; le[x] = KK;
	e[++KK] = (node){0, x, le[y], -va, KK - 1}; le[y] = KK;
}

queue <int> q;
int dis[2501], deg[2501];
bool in[2501];

bool SPFA() {
	memset(dis, 0x7f, sizeof(dis));
	memset(deg, 0, sizeof(deg));
	memcpy(lee, le, sizeof(lee));
	q.push(S); dis[S] = 0; in[S] = 1; deg[S] = 1;
	while (!q.empty()) {
		int now = q.front(); q.pop();
		for (int i = le[now]; i; i = e[i].nxt)
			if (e[i].x && dis[e[i].to] > dis[now] + e[i].cst) {
				dis[e[i].to] = dis[now] + e[i].cst;
				deg[e[i].to] = deg[now] + 1;
				if (!in[e[i].to]) {
					in[e[i].to] = 1; q.push(e[i].to);
				}
			}
		in[now] = 0;
	}
	return dis[T] != dis[0];
}

int dfs(int now, int sum) {
	if (now == T) return sum;
	
	int go = 0; in[now] = 1;
	for (int i = le[now]; i; i = e[i].nxt)
		if (e[i].x && dis[e[i].to] == dis[now] + e[i].cst && deg[e[i].to] == deg[now] + 1 && !in[e[i].to]) {
			int this_go = dfs(e[i].to, min(e[i].x, sum - go));
			if (this_go) {
				e[i].x -= this_go; e[e[i].op].x += this_go;
				go += this_go; if (go == sum) {in[now] = 0; return go;}
			}
		}
	if (go != sum) dis[now] = -1;
	in[now] = 0; return go;
}

pair <int, int> dinic() {
	pair <int, int> re = make_pair(0, 0);
	while (SPFA()) {
		int x = dfs(S, INF);
		re.first += x; re.second += dis[T] * x;
	}
	return re;
}

int main() {
//	freopen("bibliotheca.in", "r", stdin);
//	freopen("bibliotheca.out", "w", stdout);
	
	scanf("%d %d", &n, &k);
	tot = 2 * n; S = ++tot; T = ++tot;
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
	}
	for (int i = 1; i <= n; i++) {
		scanf("%d", &c[i]);
	}
	for (int i = 1; i <= n; i++) {
		add(S, i, 1, c[a[i]]); add(i + n, T, 1, 0); add(i, i + n, 1, 0);
		if (lst[a[i]]) add(i - 1, lst[a[i]] + n, 1, -c[a[i]]);
		lst[a[i]] = i;
		if (i != n) add(i, i + 1, k - 1, 0);
	}
	
	pair <int, int> re = dinic();
	if (re.first != n) return -1;
		else printf("%d", re.second);
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值