题解 P2792【[JSOI2008]小店购物】

题目链接

朱刘算法好,朱刘算法妙,朱刘算法……

题目分析

题目要求购买所有需要商品的最小花费值。而重点在于优惠方案

如果您在本店购买了商品A的话,您就可以以P元/件的优惠价格购买商品B(购买的数量不限)。

意思就是,假设优惠方案为 \(A\)\(B\),则只要购买 \(A\)的数量 \(\ge\) \(1\),就可以得到优惠。

解题思路

题目很明显是最小树形图,如果不会,出左转。

那么重点就在于如何建边

可以先假设每种商品都买了一件,那么所有优惠方案都可以使用。就直接循环得出每件商品的最低价格,求出剩余件数的费用和

然后在前面假设情况(每种购买一件)的最优方案。

仔细想想,就是求一个无根有向图的最小树形图。在一个优惠方案中建一条边,将每个商品与虚拟根连一条边,跑朱刘就行啦。

注意点:

  • 如果一个商品不需要购买直接跳过,因此要先判断,重新给每种商品编号。
	if(!t[i]) continue;
	tag[i] = ++num;

  • 同样,如果一个优惠方案中任意一件不需要购买,也直接跳过。
	if(!tag[b] || !tag[c]) continue;
  • 最后注意建的是有向边,千万不要手残。

朱刘模板就不用说了吧?我也不知道怎么讲

完整代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define rt register int
const int N = 55;
const double inf = 1e8;
int n,s,tot,cnt,num,id[N],tag[N],t[N],pre[N],vis[N];
double val[N],dis[N],res;
/*
	id:从属的连通块编号
	tag:商品重新的编号
	t:需要购买的数量
	cnt:连通块数量 
	tot:建边数量 
	pre/vis/dis: :朱刘算法所需的模板数组
*/
struct node {
	int fro,to;double w;
}e[N * N];
inline void zhuliu(int root) {
	int u,v,ver;
	while(1) {
		for(rt i = 1; i <= num; i ++) dis[i] = inf;
		for(rt i = 1; i <= tot; i ++) {
			u = e[i].fro, v = e[i].to;
			if(u != v && dis[v] > e[i].w) pre[v] = u, dis[v] = e[i].w;
		}
		for(rt i = 1; i <= num; i ++) {
			if(i != root && dis[i] == inf) return;
		}
		cnt = 0;
		memset(id,0,sizeof(id));
		memset(vis,0,sizeof(vis));
		dis[root] = 0;
		for(rt i = 1; i <= num; i ++) {
			res += dis[i], ver = i;
			while(vis[ver] != i && !id[ver] && ver != root) {
				vis[ver] = i, ver = pre[ver];
			}
			if(ver != root && !id[ver]) {
				id[ver] = ++cnt;
				for(rt j = pre[ver]; j != ver; j = pre[j]) id[j] = cnt;
			}
		}
		if(!cnt) break;
		for(rt i = 1; i <= num; i ++) {
			if(!id[i]) id[i] = ++cnt;
		}
		for(rt i = 1; i <= tot; i ++) {
			u = e[i].fro, v = e[i].to;
			e[i].fro = id[u], e[i].to = id[v];
			if(id[u] != id[v]) e[i].w -= dis[v];
		}
		num = cnt, root = id[root];
	}
}
inline void read(int &x) {
	x = 0;
	char s = getchar();
	while(s < '0' || s > '9') s = getchar();
	while(s <= '9' && s >= '0') x = x * 10 + s - '0', s = getchar(); 
}
int main() {
	read(n);
	s = ++num;
	int b,c,k;
	double a;
	for(rt i = 1; i <= n; i ++) {
		dis[i] = inf;
		scanf("%lf",&a); read(t[i]);
		if(!t[i]) continue;
		tag[i] = ++num;
		dis[i] = min(dis[i],a);
		e[++tot] = (node) {s,num,a};
	}
	read(k);
	while(k--) {
		read(b), read(c), scanf("%lf",&a);
		if(!tag[b] || !tag[c]) continue;
		e[++tot] = (node) {tag[b],tag[c],a};
		dis[c] = min(dis[c],a);
	}
	for(rt i = 1; i <= n; i ++) {
		if(t[i] > 1) res += dis[i] * (t[i] - 1);//记得减 1 
	}
	zhuliu(s);
	printf("%.2lf",res);
	return 0;
}
posted @ 2021-01-14 22:08  Spring-Araki  阅读(76)  评论(0)    收藏  举报