题解 P2792 [JSOI2008]小店购物

Posted colazcy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了题解 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;

以上是关于题解 P2792 [JSOI2008]小店购物的主要内容,如果未能解决你的问题,请参考以下文章

Luogu2792 JSOI2008 小店购物 最小树形图

JSOI2008 小店购物

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

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

网易严选京东天猫网易考拉海购微小店,5种电商类原型模板集合(免费使用)

小红书小店怎么铺货