bzoj3875: [Ahoi2014&Jsoi2014]骑士游戏 spfa处理有后效性动规

bzoj3875: [Ahoi2014&Jsoi2014]骑士游戏

Description

【故事背景】
长期的宅男生活中,JYY又挖掘出了一款RPG游戏。在这个游戏中JYY会
扮演一个英勇的骑士,用他手中的长剑去杀死入侵村庄的怪兽。
【问题描述】
在这个游戏中,JYY一共有两种攻击方式,一种是普通攻击,一种是法术攻
击。两种攻击方式都会消耗JYY一些体力。采用普通攻击进攻怪兽并不能把怪兽彻底杀死,怪兽的尸体可以变出其他一些新的怪兽,注意一个怪兽可能经过若干次普通攻击后变回一个或更多同样的怪兽;而采用法术攻击则可以彻底将一个怪兽杀死。当然了,一般来说,相比普通攻击,法术攻击会消耗更多的体力值(但由于游戏系统bug,并不保证这一点)。
游戏世界中一共有N种不同的怪兽,分别由1到N编号,现在1号怪兽入
侵村庄了,JYY想知道,最少花费多少体力值才能将所有村庄中的怪兽全部杀死呢?

Input

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

Output

输出一行一个整数,表示最少需要的体力值。

Sample Input

4
4 27 3 2 3 2
3 5 1 2
1 13 2 4 2
5 6 1 2

Sample Output

26

HINT

【样例说明】
首先用消耗4点体力用普通攻击,然后出现的怪兽编号是2,2和3。花费
10点体力用法术攻击杀死两个编号为2的怪兽。剩下3号怪兽花费1点体力进
行普通攻击。此时村庄里的怪兽编号是2和4。最后花费11点体力用法术攻击
将这两只怪兽彻底杀死。一共花费的体力是4+5+5+1+5+6=26。
【数据范围】
2<=N<=2*10^5,1<=Ri,Sigma(Ri)<=10^6,1<=Ki,Si<=5*10^14

分析

动规方程应该很好写吧
f[i]=min(s[i]+Connect(i,j)f[j],k[i]) f [ i ] = m i n ( s [ i ] + ∑ C o n n e c t ( i , j ) f [ j ] , k [ i ] )
然后就发现这个东西有后效性。
如果是拓扑序DP的话,有后效性(即形成环)是使用高斯消元。
但是对于线性DP,这个东西可以用Spfa处理。
对于每个转移连边,如果当前状态有更优解,入队列即可。
其实这道鬼题目可以用贪心写的。。

代码

/**************************************************************
    Problem: 3875
    User: 2014lvzelong
    Language: C++
    Result: Accepted
    Time:5792 ms
    Memory:22580 kb
****************************************************************/

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 2e5+50, M = 1e6 + 50;
long long read() {
    char ch = getchar(); long long x = 0, f = 1;
    for(;ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;;
    for(;ch >= '0' && ch <= '9';ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
    return x * f;
}
int to[M], nxt[M], pre[N], to2[M], nxt2[M], pre2[N], q[N], top, n;
long long d[N], s[N];
bool vis[N];
void adds(int u, int v) {
    to[++top] = v; nxt[top] = pre[u]; pre[u] = top;
    to2[top] = u; nxt2[top] = pre2[v]; pre2[v] = top;
}
int Move(int &x) {++x; if(x == n + 1) x = 0; return x;}
void spfa() {
    int L = 0, R = n;
    for(int i = 1;i <= n; ++i) vis[q[i] = i] = true;
    while(L != R) {
        int u = q[Move(L)]; vis[u] = false;
        long long tmp = s[u];
        for(int i = pre[u]; i; i = nxt[i])
            tmp += d[to[i]];
        if(tmp > d[u]) continue;
        d[u] = tmp;
        for(int i = pre2[u]; i; i = nxt2[i])
            if(!vis[to2[i]]) vis[q[Move(R)] = to2[i]] = true;
    }
}

int main() {
    n = read();
    for(int i = 1;i <= n; ++i) {
        s[i] = read(); d[i] = read(); 
        for(int k = read(), j = 1;j <= k; ++j)
            adds(i, read());
    }
    spfa();
    printf("%lld\n", d[1]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值