骑士游戏-最短路变种

来源

Problem #3373 - ECNU Online Judge

题意

长期的宅男生活中,JYY 又挖掘出了一款 RPG 游戏。在这个游戏中 JYY 会扮演一个英勇的骑士,用他手中的长剑去杀死入侵村庄的怪兽。

在这个游戏中,JYY 一共有两种攻击方式,一种是普通攻击,一种是法术攻击。两种攻击方式都会消耗 JYY 一些体力。采用普通攻击进攻怪兽并不能把怪兽彻底杀死,怪兽的尸体可以变出其他一些新的怪兽,注意一个怪兽可能经过若干次普通攻击后变回一个或更多同样的怪兽;而采用法术攻击则可以彻底将一个怪兽杀死。当然了,一般来说,相比普通攻击,法术攻击会消耗更多的体力值(但由于游戏系统 bug,并不保证这一点)。

游戏世界中一共有  \(N\) 种不同的怪兽,分别由 \(1\) 到 \(N\) 编号,现在 \(1\) 号怪兽入侵村庄了,JYY 想知道,最少花费多少体力值才能将所有村庄中的怪兽全部杀死呢?

输入

接下来  \(N\) 行,每行描述一个怪兽的信息;其中第 \(i\) 行包含若干个整数,前三个整数为 ,表示对于 \(i\) 号怪兽,普通攻击需要消耗 \(S_i\) 的体力,法术攻击需要消耗 \(K_i\) 的体力,同时如果采用普通攻击 \(i\) 号怪兽死亡后会产生 \(R_i\) 个新的怪兽。接下来 \(R_i\) 个整数,表示新出现的怪兽编号。同一编号的怪兽可以出现多个。

题解

\(dist[i]\) 表示彻底消灭怪兽 \(i\) 的最小代价。
彻底消灭怪兽有两种方法,法术攻击怪兽,或普通攻击后彻底消灭其产生的怪兽。

显然有

\[dist[i] = \min(K_i,S_i+\sum_{j\in R_i} dist[j]) \]

当我们获取到一个更优的子状态时,我们可以用该子状态更新父状态,如果父状态变得更优了,我们就可以考虑更新父状态的父状态......

此操作可以类比最短路径松弛操作

\[dist[v] = \min(dist[v],dist[u]+w(u,v)) \]

当我们已经获取到\(dist[u]\)时,考虑用\(dist[u]\)\(dist[v]\)进行转移,也是同样的想法。

\(dist\)数组初始化,即直接使用法术消灭怪兽的代价

\[dist[i] \leftarrow K[i] \]

\(a[i]\)数组表示使用物理攻击彻底消灭怪兽的代价

\[a[i]\leftarrow S[i] + \sum_{j\in R_i} K[j] \]

所以有

\[dist[i]\leftarrow \min(dist[i],a[i]) \]

此时\(dist\)数组初始化完毕,每次取出dist数组中最小的那个点 \(i\)(最小意味着无法再优化,已达到最优),考虑点 \(i\) 的父节点 \(j\) 的松弛操作。建立一个最小堆,每次弹出堆顶元素。即可。

此时彻底消灭点\(i\)的代价为\(dist[i]\)已更新至最优,则彻底消灭节点\(j\)的代价可以优化为

\[dist[j] = S[j] +\sum_{k\in R_i,k\not =i} K[k] + dist[i] \]

\[dist[j] = \min(dist[j],dist[j] - (K[i]-dist[i])) \]

\(dist[j] - (K[i]-dist[i]) < dist[j]\),则松弛成功,考虑继续向上传播松弛(加入最小堆)。

AC代码

#include <bits/stdc++.h>
#define ll long long
//#define int long long
#define IOS ios::sync_with_stdio(false);cin.tie(nullptr);
#define endl '\n' 
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3f;
const double PI = acos(-1.0);
using namespace std;
//ifstream fin("input.txt");
//ofstream fout("output.txt");
//#define cin fin
//#define cout fout
const int N = 2e5+5;
ll S[N],K[N];
vector<int> R[N];
vector<int> p[N];
ll dist[N];
ll a[N];
bool vis[N];
int n;
signed main()
{
    IOS
    cin>>n;
    for(int i=1;i<=n;i++) {
        cin>>S[i]>>K[i];
        int r;cin>>r;
        R[i].resize(r);
        for(int j=0;j<r;j++) {
            cin>>R[i][j];
            p[R[i][j]].push_back(i);
        }
    }
    // 初始化dist
    for(int i=1;i<=n;i++) {
        dist[i] = K[i];
    }
    priority_queue<pair<ll,int>> pq;
    for(int i=1;i<=n;i++) {
        a[i] = S[i];
        for(auto j:R[i]) {
            a[i] += K[j];
        }
        if(a[i]<dist[i]) dist[i]=a[i];
        pq.push({-dist[i],i});
    }
    // 类似dijkstra算法的贪心和松弛思想
    while(!pq.empty()) {
        auto [_,u] = pq.top(); pq.pop();
        if(vis[u]) continue;
        vis[u]=1;
        for(int v:p[u]) {
            a[v] -= (K[u]-dist[u]);
            if(a[v]<dist[v]) {
                dist[v] = a[v];
                pq.push({-dist[v],v});
            }
        }
    }
    cout<<dist[1];
    //fin.close(),fout.close();
    return 0;
}
posted @ 2025-12-11 19:42  NightRainLone  阅读(8)  评论(0)    收藏  举报