JSOI2014骑士游戏(最短路)
题目背景
长期的宅男生活中,JYY又挖掘出了一款RPG游戏。在这个游戏中JYY会
扮演一个英勇的骑士,用他手中的长剑去杀死入侵村庄的怪兽。
题目描述
在这个游戏中,JYY一共有两种攻击方式,一种是普通攻击,一种是法术攻击。两种攻击方式都会消耗JYY一些体力。采用普通攻击进攻怪兽并不能把怪兽彻底杀死,怪兽的尸体可以变出其他一些新的怪兽,注意一个怪兽可能经过若干次普通攻击后变回一个或更多同样的怪兽;而采用法术攻击则可以彻底将一个怪兽杀死。当然了,一般来说,相比普通攻击,法术攻击会消耗更多的体力值(但由于游戏系统bug,并不保证这一点)。
游戏世界中一共有N种不同的怪兽,分别由1到N编号,现在1号怪兽入侵村庄了,JYY想知道,最少花费多少体力值才能将所有村庄中的怪兽全部杀死呢?
输入输出格式
输入格式:
第一行包含一个整数N。
接下来N行,每行描述一个怪兽的信息;
其中第i行包含若干个整数,前三个整数为Si,Ki和Ri,表示对于i号怪兽,普通攻击需要消耗Si的体力,法术攻击需要消耗Ki的体力,同时i号怪兽死亡后会产生Ri个新的怪兽。表示一个新出现的怪兽编号。同一编号的怪兽可以出现多个。
输出格式:
输出一行一个整数,表示最少需要的体力值。
这道题去年十一小长假考过,当时一脸懵逼不会做,
考虑打怪兽的方式,如果我打的怪兽是只打的,那他分裂后成为的小怪兽一定是小的(这是最优情况下的必然),所以我们按照怪兽的法术抗性加入小根堆中,用一种既像拓扑又像最短路的东西去维护它。
具体做法为:建一张图连向打死后生成的怪兽,在建一张反图,每次取出堆顶后,扫描反边,将反边的入度减去,若如度为0,则扫描正边,看看是否可以更新原来的dp值,若成功更新,则把更新过后的点加入堆。
这样做为什么是对的呢(其实我也不知道)。
显然到最后我们肯定是要用法术攻击的,如果底层的法术攻击代价比上面的点代价还要大,那肯定不是最优解,我们肯定不去取,所以当一个点被更新的条件时当且仅当它的儿子的代价都要比他小时。
下面的话是我这篇题解的参考,并不是我写的。
有种解法是spfa,将法术攻击每个怪兽的体力值当做初始的路径长度,之后优化一个dis之后,将其推进队列,再更新其他怪兽。
如果按照打死每种怪兽的最小体力给怪兽排序的话,可知,假如一个耗体力大的怪兽是被普通攻击了,那么其变成的怪兽一定排在它前面(体力值比他小)。换言之,按照所耗体力值从小到大枚举每个怪兽,由当前枚举过的怪兽更新没有被枚举怪兽的体力值,再找到没有被枚举的怪兽中体力值最小的怪兽继续枚举,枚举的顺序一定是按照打死每种怪兽的最小体力递增排序的,当前体力值也是最小体力值。
每次拿出堆顶,更新刚好达到条件的点(条件即为该点所有出边指向的点都已出堆),如果更优则推入堆中,重复这个过程。
发现暴力判断条件的话时间复杂度显然不对,我们可以用拓扑的思想,建一张反图,每出堆一个点就将其连向的点入度-1,直至入度为0的时候更新该点。
时间复杂度O(nlogn+m)
下面贴代码
#include<iostream> #include<algorithm> #include<cstdio> #include<queue> #define N 200009 using namespace std; int n,r[N],x,tot,head[N],head1[N],tot1; long long d[N],k[N]; bool vis[N]; struct w { int n,to; }an[2000009],an1[2000009]; inline void add1(int u,int v) { an1[++tot1].n=head1[u]; an1[tot1].to=v; head1[u]=tot1; } inline void add(int u,int v) { an[++tot].n=head[u]; an[tot].to=v; head[u]=tot; } struct ha { long long val; int pos; }; bool operator < (ha a,ha b) { return a.val>b.val; } priority_queue<ha>q; int main() { scanf("%d",&n); for(int i=1;i<=n;++i) { scanf("%lld%lld%d",&d[i],&k[i],&r[i]); for(int j=1;j<=r[i];++j) { scanf("%d",&x); add(x,i); add1(i,x); } if(!r[i])k[i]=min(k[i],d[i]); q.push(ha{k[i],i}); } while(!q.empty()) { ha u=q.top();q.pop(); if(k[u.pos]<u.val)continue; if(vis[u.pos])continue; vis[u.pos]=1; for(int i=head[u.pos];i;i=an[i].n) { int v=an[i].to; r[v]--; if(r[v]<=0&&!vis[v]) { long long ss=d[v]; for(int j=head1[v];j;j=an1[j].n) ss+=k[an1[j].to]; if(k[v]>ss) { k[v]=ss; q.push(ha{k[v],v}); } } } } cout<<k[1]; }