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

题目链接

Solution [JSOI2008]小店购物

题目大意:有若干件物品,每个物品有一个原价,购买某件物品后可以以更优价购买另一件物品.每件物品有一个需求数目,既不能多买,也不能少买(如果需求\(0\)件你就不能买,哪怕可能使得总价最优)

题目分析:看到题解区巨佬的题解,发现此题有一个绝妙的贪心做法.

对于某件物品,我们怎样使得购买它的代价最小呢?我们可以贪心的在这件物品所有的可行方案(原价与优惠价)里面取最小的,做一次乘法运算即可得出答案

关键是,怎样使得这个贪心是正确的呢?我们发现,假如所有物品都被购买了的话,那么就可以保证这个贪心是正确的.那么我们就可以把这个问题分成两个问题:

  • 1.求出每个物品都买一件的最小代价和
  • 2.所有物品都购买了一件后,我们贪心算出剩余物品的代价和

那么问题\(1\)怎么解决?

首先,我们对于每一个优惠方案\((a,b,p)\),都在图中从\(a\)往\(b\)连一条权值为\(p\)的边,这样我们就处理完了优惠方案

对于原价,我们新建一个虚拟节点,并从这个虚拟节点往所有点连一条边,权值为这个物品的原价,这样原图就是一个以虚拟节点为根的有向图

然后,你发现我们这个问题是不是有几个美妙的性质:

  • \(1.\)有向图有根节点
  • \(2.\)所有点都必须被选到(对于那些不需要的点,我们假设它需要,用些处理技巧使它不影响最终答案)
  • \(3.\)使得边权之和最小

然后这不就是最小树形图的板子题了么?

上面所说的处理技巧:

对于一个优惠方案\((a,b)\)我们可以分类讨论:

  • 如果物品\(a\)不需要:那么我们从\(a\)到\(b\)连一条权值为\(INF\)的边.道理很好想,既然物品\(a\)不能选择,那么选择物品\(a\)的优惠方案肯定都是非法的,把权值设为\(INF\)就保证这个方案不会被选到
  • 如果物品\(b\)不需要:那么我们从\(a\)到\(b\)连一条权值为\(0\)的边.既然物品\(b\)不需要,我们可以看做购买物品\(b\)不需要任何代价

对于原价的方案,处理起来差不多,详细都在代码注释里了:

#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 64;
const int maxm = maxn * maxn;//总边数没有给出,不过是可以算出来的
const double INF = 1e9;
struct Edge{
    int from,to;
    double dist;
    Edge() = default;
    Edge(int a,int b,double c):from(a),to(b),dist(c){}
}Edges[maxm];
int head[maxn],nxt[maxm],n,m;
inline void addedge(int from,int to,double dist){
    static int tot;
    Edges[++tot] = Edge(from,to,dist);
    nxt[tot] = head[from];
    head[from] = tot;
}
double ine[maxn],ans;
int pre[maxn],vis[maxn],id[maxn],num[maxn],root;//num为每个物品的需求数量
inline void work(){//以下为最小树形图模板(图论的关键不都是建图么)
    while(true){
        for(int i = 1;i <= n;i++)ine[i] = INF;
        for(int i = 1;i <= m;i++){
            int u = Edges[i].from,v = Edges[i].to;
            if(u != v && Edges[i].dist < ine[v])
                ine[v] = Edges[i].dist,pre[v] = u;
        }
        int cnt = 0;
        memset(vis,0,sizeof(vis));
        memset(id,0,sizeof(id));
        for(int i = 1;i <= n;i++){
            if(i == root)continue;
            ans += ine[i];
            int v = i;
            while(vis[v] != i && !id[v] && v != root)
                vis[v] = i,v = pre[v];
            if(!id[v] && v != root){
                id[v] = ++cnt;
                for(int u = pre[v];u != v;u = pre[u])
                    id[u] = cnt;
            }
        }
        if(cnt == 0)break;
        for(int i = 1;i <= n;i++)
            if(!id[i])id[i] = ++cnt;
        for(int i = 1;i <= m;i++){
            int u = Edges[i].from,v = Edges[i].to;
            Edges[i].from = id[u],Edges[i].to = id[v];
            if(id[u] != id[v])Edges[i].dist -= ine[v];
        }
        root = id[root];
        n = cnt;
    }
}
double price[maxn];//每个物品的最小代价
int main(){
    scanf("%d",&n);
    for(int i = 1;i <= n;i++)//读入每个物品的原价以及需求数量
        scanf("%lf %d",&price[i],&num[i]);
    for(int i = 1;i <= n;i++)//从虚拟节点连边(原价方案)
        addedge(n + 1,i,num[i] == 0 ? 0 : price[i]);//处理不需要的点,使它不影响最终结果
    scanf("%d",&m);
    for(int i = 1;i <= m;i++){//处理优惠方案
        int a,b;double c;
        scanf("%d %d %lf",&a,&b,&c);
        if(num[a] == 0)addedge(a,b,INF);//分类讨论
        else if(num[b] == 0)addedge(a,b,0);
        else addedge(a,b,c),price[b] = min(price[b],c);//更新购买每件物品的最优价格,便于下面贪心
    }
    for(int i = 1;i <= n;i++)//贪心购买物品
        ans += (num[i] - 1) * price[i];
    root = n + 1;//根
    m = m + n;//连接了虚拟边,边数要相应增加
    n = n + 1;//同上
    work();//最小树形图,求出每个物品都买一件的最小代价
    printf("%.2lf\n",ans);
    return 0;
}

原文地址:https://www.cnblogs.com/colazcy/p/11514956.html

时间: 2024-08-03 13:39:43

题解 P2792 【[JSOI2008]小店购物】的相关文章

JSOI2008 小店购物

https://www.luogu.org/problem/show?pid=2792 题目背景 JSOI集训队的队员发现,在他们经常活动的集训地,有一个小店因为其丰富的经营优惠方案深受附近居民的青睐,生意红火. 题目描述 小店的优惠方案十分简单有趣: 一次消费过程中,如您在本店购买了精制油的话,您购买香皂时就可以享受2.00元/块的优惠价:如果您在本店购买了香皂的话,您购买可乐时就可以享受1.50元/听的优惠价......诸如此类的优惠方案可概括为:如果您在本店购买了商品A的话,您就可以以P元

【LuoguP2792 】[JSOI2008]小店购物(最小树形图)

题目链接 题目描述 小店的优惠方案十分简单有趣: 一次消费过程中,如您在本店购买了精制油的话,您购买香皂时就可以享受2.00元/块的优惠价:如果您在本店购买了香皂的话,您购买可乐时就可以享受1.50元/听的优惠价......诸如此类的优惠方案可概括为:如果您在本店购买了商品A的话,您就可以以P元/件的优惠价格购买商品B(购买的数量不限). 有趣的是,你需要购买同样一些商品,由于不同的买卖顺序,老板可能会叫你付不同数量的钱.比如你需要一块香皂(原价2.50元).一瓶精制油(原价10.00元).一听

Luogu2792 JSOI2008 小店购物 最小树形图

传送门 被题意杀 本以为一个种类的物品一定要一起买 看了题解才知道可以先把所有要买的物品买一个,剩下要买的物品就可以得到这个种类的物品能够得到的最大优惠-- 所以现在只需要知道:第一次买所有物品一遍时按照什么顺序买最优惠 建一个超级源点向每一个物品连权值等同于其价值的边,对于优惠\((A,B,P)\)从\(A\)向\(B\)连权值为\(P\)的遍,然后一遍最小树形图即可. 注意一个购买数量为\(0\)的点和它的所有出入边都要被忽视 #include<bits/stdc++.h> //This

LUOGU 2792: [JSOI2008]小店购物 最小树形图

title LUOGU 2792 简化题意: 若干件物品,每个物品有一个原价,购买某件物品后可以以更优价购买另一件物品.每件物品有一个需求数目,既不能多买,也不能少买(如果需求 0 件就不能买,哪怕可能使得总价最优). analysis 这题有一个很棒的贪心算法,对于某件物品,我们怎样使得购买它的代价最小呢? 我们可以贪心的在这件物品所有的可行方案(原价与优惠价)里面取最小的,做一次乘法运算即可得出答案. 关键是,怎样使得这个贪心是正确的呢?我们发现,假如所有物品都被购买了的话,那么就可以保证这

前有淘宝后有“微信小店” 京东怎么玩拍拍?

浓缩观点 就在京东沉醉在微信开放了一级入口的时候,腾讯悄然上线了微信小店,这个无厘头的开幕,让准备在微信搭台唱戏的京东,顿时冷了下场. 我认为京东上市以来,干的最漂亮的一件事就是"重启"了拍拍网,即完成了腾讯交付的"政治任务",又很好地提升了股价,还顺带恶心了把淘宝,一箭三雕. 不过,好戏没玩,就在京东沉醉在微信开放了一级入口的时候,腾讯今天悄然上线了微信小店,这个无厘头的开幕,让准备在微信搭台唱戏的京东,顿时冷了下场. 且不说,微信小店能做成什么样,但是腾讯内部已

微信小程序购物商城系统开发系列-目录结构

上一篇我们简单介绍了一下微信小程序的IDE(微信小程序购物商城系统开发系列-工具篇),相信大家都已经蠢蠢欲试建立一个自己的小程序,去完成一个独立的商城网站. 先别着急我们一步步来,先尝试下写一个自己的小demo. 这一篇文章我们主要的是介绍一下小程序的一些目录结构,以及一些语法,为我们后面的微信小程序商城系统做铺垫. 首先我们来了解下小程序的目录结构 Pages 我们新建的一些页面将保存在这个文件夹下面,每一个小程序页面是由同路径下同名的四个不同后缀文件的组成,如:index.js.index.

微信公众平台支持接收和发送微信小视频 微信小店产品展示更全方位

微信公众号支持小视频功能已上线.这个功能对微信小店来说是个大利好,产品展示更多方位,某宝颤抖了么?已微信认证的公众号可接收来自微信用户的小视频,并作为素材下发给微信用户,增强公众帐号与粉丝实时互动性. 1. 公众号接收来自微信用户的小视频,并保存到素材库. 2. 公众号将小视频插入图文消息中,并下发给微信用户.下发渠道包括群发消息.自定义回复.自动回复等.

C#开发微信门户及应用(23)-微信小店商品管理接口的封装和测试

在上篇<C#开发微信门户及应用(22)-微信小店的开发和使用>里面介绍了一些微信小店的基础知识,以及对应的对象模型,本篇继续微信小店的主题,介绍其中API接口的封装和测试使用.微信小店的相关对象模型,基本上包括了常规的商品.商品分组.货架.库存.订单这些模型,还有商品分类,商品分类属性.商品分类SKU.快递邮寄模板.图片管理等功能.本文介绍的接口封装也就是基于这些内容进行的,并针对接口的实现进行测试和使用. 1.商品管理接口的定义 前面文章介绍了微信小店的对象模型,如下所示. 这个图形基本上覆

业余开个小店各位码农看过来,吃核桃补补脑

最近换工作进入某程工作比较清闲 经常有同事托我代买杭州的临安核桃 为了方便同事(其实感觉走淘宝好收钱了哈) 开了家淘宝小店 翠花喵 其实我家喵喵的名字... 翠花喵淘宝旗舰店 主营纸皮核桃和山核桃,,,纸皮核桃是炒熟奶油口味,,,吃起来很方便而且味道超好... 希望各位童鞋多多捧场... 有空进淘宝收藏下我店铺和商品哈