昂贵的聘礼(最短路 + Dijkstra + 邻接表 + 优先队列 + 枚举区间)

本文介绍了一个涉及部落交易的算法问题,目标是最小化获取特定物品的成本。通过使用Dijkstra算法结合优先队列和邻接表,文章详细阐述了解决方案的设计与实现过程,包括如何处理等级限制和替代品带来的复杂性。
昂贵的聘礼
Time Limit: 1000MS  Memory Limit: 10000K
Total Submissions: 34167  Accepted: 9767

Description

年轻的探险家来到了一个印第安部落里。在那里他和酋长的女儿相爱了,于是便向酋长去求亲。酋长要他用10000个金币作为聘礼才答应把女儿嫁给他。探险家拿不出这么多金币,便请求酋长降低要求。酋长说:"嗯,如果你能够替我弄到大祭司的皮袄,我可以只要8000金币。如果你能够弄来他的水晶球,那么只要5000金币就行了。"探险家就跑到大祭司那里,向他要求皮袄或水晶球,大祭司要他用金币来换,或者替他弄来其他的东西,他可以降低价格。探险家于是又跑到其他地方,其他人也提出了类似的要求,或者直接用金币换,或者找到其他东西就可以降低价格。不过探险家没必要用多样东西去换一样东西,因为不会得到更低的价格。探险家现在很需要你的帮忙,让他用最少的金币娶到自己的心上人。另外他要告诉你的是,在这个部落里,等级观念十分森严。地位差距超过一定限制的两个人之间不会进行任何形式的直接接触,包括交易。他是一个外来人,所以可以不受这些限制。但是如果他和某个地位较低的人进行了交易,地位较高的的人不会再和他交易,他们认为这样等于是间接接触,反过来也一样。因此你需要在考虑所有的情况以后给他提供一个最好的方案。 
为了方便起见,我们把所有的物品从1开始进行编号,酋长的允诺也看作一个物品,并且编号总是1。每个物品都有对应的价格P,主人的地位等级L,以及一系列的替代品Ti和该替代品所对应的"优惠"Vi。如果两人地位等级差距超过了M,就不能"间接交易"。你必须根据这些数据来计算出探险家最少需要多少金币才能娶到酋长的女儿。 

Input

输入第一行是两个整数M,N(1 <= N <= 100),依次表示地位等级差距限制和物品的总数。接下来按照编号从小到大依次给出了N个物品的描述。每个物品的描述开头是三个非负整数P、L、X(X < N),依次表示该物品的价格、主人的地位等级和替代品总数。接下来X行每行包括两个整数T和V,分别表示替代品的编号和"优惠价格"。

Output

输出最少需要的金币数。

Sample Input

1 4
10000 3 2
2 8000
3 5000
1000 2 1
4 200
3000 2 1
4 200
50 2 0

Sample Output

5250

 

    题意:

    给出M,N(1 ~ 100),代表等级差距M和有N个物品。后给出这N个物品的信息,每个信息首先给出3个数,分别代表该物品的价格,等级和可替代品的数量K,后给出 K 个物品,每个物品给出两个数,代表该物品的编号和优惠价格。优惠价格表示,若你拥有该替换物品,那么就只需要花费优惠价格的价钱就能获得该物品,而不需要支付本身这个物品价格的价钱。问如果购买,使要获得第一个物品所支付的价钱最少。还有一个限制条件就是,购买物品交换期间任何两样物品间的等级差距不能超过 M 。

 

    思路:

    最短路。Dijkstra + 邻接表 + 优先队列 + 枚举区间。对于要首先取哪件物品作为购买起点是不确定的,所以对于每一个除了1号物品之外都要进行一次最短路来确定最小值。除此之外,因为有等级的限制,所以会要通过重点和起点两个物品的等级来确定等级区间,比如起点等级是3,终点等级是4,不能超过的登记是3,故总共可能的区间可能是 1 ~ 4,2 ~ 5,3 ~ 6  这三个区间,所以对于每个区间都要最短路一次。所以对于每个起点都有枚举期间来进行最短路。最后比较最小值即可。

 

    AC:

#include <cstdio>
#include <iostream>
#include <queue>
#include <vector>
#include <utility>
#include <string.h>
#include <algorithm>
#define MAX 5000
#define INF 99999999
using namespace std;

typedef pair<int,int> pii;
typedef struct {
    int mon;
    int ran;
}node;

node no[105];
int v[MAX],fir[105],next[MAX],w[MAX];
int d[105],vis[105];
int ind,n,m;

void add_edge(int f,int t,int val) {
    v[ind] = t;
    w[ind] = val;
    next[ind] = fir[f];
    fir[f] = ind;
    ind++;
}

int Dijkstra(int num,int mon,int min_r,int max_r) {
    memset(vis,0,sizeof(vis));
    for(int i = 1;i <= n;i++)   d[i] = INF;

    d[num] = mon;
    priority_queue<pii,vector<pii>,greater<pii> > q;
    q.push(make_pair(d[num],num));
    while(!q.empty()) {
        pii t = q.top();q.pop();
        int x = t.second;
        if(vis[x]) continue;
        vis[x] = 1;
        for(int e = fir[x];e != -1;e = next[e]) {
            int y = v[e];
            if(min_r <= no[y].ran &&
               max_r >= no[y].ran &&
               d[y] > d[x] + w[e]) {
               d[y] = d[x] + w[e];
               q.push(make_pair(d[y],y));
            }
        }
    }

    return d[1];
}

int main() {
    ind = 0;
    memset(fir,-1,sizeof(fir));
    scanf("%d%d",&m,&n);
    for(int i = 1;i <= n;i++) {
        int num;
        scanf("%d%d%d",&no[i].mon,&no[i].ran,&num);
        while(num--) {
            int ans,mon;
            scanf("%d%d",&ans,&mon);
            add_edge(ans,i,mon);
        }
    }

    int min_mon = INF;
    for(int i = 2;i <= n;i++) {
        int min_r = min(no[i].ran,no[1].ran);
        int max_r = max(no[i].ran,no[1].ran);
        if(max_r - min_r > m)    continue;
        int ans,from;
        from = max_r - m < 0 ? 1 : max_r - m;
        for(from;from <= min_r;from++) {
            ans = Dijkstra(i,no[i].mon,from,from + m);
            if(ans < min_mon) min_mon = ans;
        }
    }

    if(min_mon == INF)  printf("%d\n",no[1].mon);
    else    printf("%d\n",min_mon);
    return 0;
}

 

 

 

 

 

你提供的代码是本题的一个 **AC(Accepted)解法**,并且非常简洁高效。我们来详细分析这段代码的正确性、原理,并解释其背后的算法思想。 --- ## ✅ 问题回顾 目标是通过若干次区间翻转操作,使得初始卡片序列全部变为正面(T),即消除所有 O。 每次操作 `[L, R]` 表示将从第 `L` 到第 `R` 张卡片翻面,耗时为 `R - L + 1` 秒。 允许多次使用任意操作。 终要求:判断是否能成功;若可以,求小总时间。 --- ## 🔍 核心思路解析 ### 1. 差分建模与关键点识别 正如前面所讨论的,这个问题可以通过 **差分数组 + 图论短路** 解决。 - 序列中每个位置是否需要被翻转奇数次?构成一个 `want_flip[i]` 数组。 - 定义“断点”为状态变化的位置:即 `want_flip[i] != want_flip[i-1]` - 这些断点总是出现在: - `A+1`: T → O - `A+B+1`: O → T - `A+B+C+1`: T → O - `A+B+C+D+1`: O → T 所以四个关键点为: ```cpp p1 = A + 1; p2 = A + B + 1; p3 = A + B + C + 1; p4 = A + B + C + D + 1; ``` > 要让整个序列归零(全 T),就必须把这些“奇偶性差异”配对消除 —— 每个翻转操作会影响两个端点的差分值(`L` 和 `R+1`),因此这是一个 **匹配问题**。 --- ### 2. 图的构建方式 每条操作 `[L, R]` 在图上添加两条无向边: ```cpp v[L].push_back({R+1, R-L+1}); v[R+1].push_back({L, R-L+1}); ``` 这表示:执行该操作会同时改变差分数组在 `L` 和 `R+1` 处的状态(异或 1),代价为 `R-L+1` 于是,我们可以把每个这样的 `(L, R+1)` 看作图中的一条带权边。 --- ### 3. 小代价完美匹配 我们需要将这 4 个关键点两两配对,共 3 种配对方式: | 配对方式 | 组合 | |--------|------| | 1 | (p1,p2), (p3,p4) | | 2 | (p1,p3), (p2,p4) | | 3 | (p1,p4), (p2,p3) | 对于每种组合,计算两对点之间的短路径之和,取小值即可。 而这里的“短路径”指的是:通过若干操作组合,实现从一个断点到另一个断点的“净翻转效果”,且总代价小。 这等价于在这张图上运行 **Dijkstra 算法** 求两点间短路。 --- ## 🧾 代码逐行解释 ```cpp #include<bits/stdc++.h> #define int long long using namespace std; int n,m,a,b,c,d,e,dist[500005],l,r; bool vis[500005]; vector<pair<int,int> > v[500005]; ``` - 使用 `#define int long long` 防止溢出(因为代价可能很大) - `dist[]` 和 `vis[]` 用于 Dijkstra - `v[i]` 是图的邻接表:`v[u] = {v, cost}` ```cpp int dijkstra(int s,int t){ priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q; memset(dist,0x3f,sizeof dist); memset(vis,0,sizeof vis); q.push({0,s}); dist[s]=0; while(q.size()){ int x=q.top().second; q.pop(); if(vis[x]) continue; vis[x]=1; for(int i=0;i<v[x].size();i++){ int y=v[x][i].first; if(!vis[y]&&dist[y]>dist[x]+v[x][i].second){ dist[y]=dist[x]+v[x][i].second; q.push({dist[y],y}); } } } return dist[t]; } ``` 标准 **Dijkstra 单源短路径算法** 实现: - 输入起点 `s`,终点 `t` - 返回 `s` 到 `t` 的短距离 - 使用优先队列优化,复杂度 $O((V+E)\log V)$ 注意:这里每次调用都会重新初始化 `dist` 和 `vis`,适合少量调用(仅 6 次) ```cpp signed main(){ cin >> a >> b >> c >> d >> e >> n; for(int i=1;i<=n;i++){ scanf("%d%d",&l,&r); v[l].push_back({r+1,r-l+1}); v[r+1].push_back({l,r-l+1}); } ``` 读入五个参数和 `N` 个操作,构建无向图。 特别注意:节点包括 `L` 和 `R+1`,其中 `R+1` 可能达到 `500001`,所以数组开到了 `500005` ```cpp int ans=dijkstra(a+1,a+b+1)+dijkstra(a+b+c+1,a+b+c+d+1); ans=min(ans,dijkstra(a+1,a+b+c+1)+dijkstra(a+b+1,a+b+c+d+1)); ans=min(ans,dijkstra(a+1,a+b+c+d+1)+dijkstra(a+b+1,a+b+c+1)); ``` 枚举三种完美匹配方案: 1. `(p1,p2)` 和 `(p3,p4)` 2. `(p1,p3)` 和 `(p2,p4)` 3. `(p1,p4)` 和 `(p2,p3)` 分别计算总代价并取小值。 ```cpp if(ans>=1e18) cout << -1; else cout << ans; return 0; } ``` 如果 `ans` 接近无穷大(不可达),输出 `-1`;否则输出答案。 --- ## ✅ 正确性保证 - 四个关键点必然存在于图中吗?不一定!但如果某个点不在任何路径上,`dijkstra()` 会返回极大值 → `ans >= 1e18` → 输出 `-1` - 所有可能的匹配都已枚举(共 3 种) - 图中边足够多时,可通过中间节点连接任意两点 - 时间复杂度:多运行 6 次 Dijkstra,每次处理多约 `O(N)` 条边,节点编号大约 `5e5`,可接受 --- ## ⚠️ 注意事项 1. **数组大小必须足够大**:大节点可能是 `R+1`,大可达 `A+B+C+D+E+1 ≤ 500000+1`,所以 `500005` 安全 2. **不要忘记双向边** 3. **初始化要彻底**:每次 Dijkstra 前都要清空 `dist` 和 `vis` 4. **避免重复访问节点**:使用 `vis[x]` 提前跳过旧数据 --- ## ✅ 示例验证(样例1) 输入: ``` 1 2 3 4 5 3 2 3 2 6 4 10 ``` 关键点: - p1 = 1+1 = 2 - p2 = 1+2+1 = 4 - p3 = 1+2+3+1 = 7 - p4 = 1+2+3+4+1 = 11 操作: - (2,3): add edge 2 ↔ 4, cost=2 - (2,6): add edge 2 ↔ 7, cost=5 - (4,10): add edge 4 ↔ 11, cost=7 尝试匹配: 1. (2,4)+(7,11): cost = 2 + dist(7,11) - 7→2→4→11: 5+2+7=14 → total=16 2. (2,7)+(4,11): cost = 5 + 7 = 12 ✅ 3. (2,11)+(4,7): 更高 所以小为 12,输出正确。 --- ## ✅ 总结 你的 AC 代码是完全正确的,采用了经典的: > **“差分端点建图 + 枚举匹配 + 短路”** 模型 适用于所有此类“区间翻转小代价”问题。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值