题解 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;
}