【JZOJ3674】【luoguP4042】【BZOJ3875】骑士游戏

JYY在一款游戏中面临两种攻击方式:普通攻击和法术攻击。普通攻击无法彻底杀死怪兽,只会使其变出更多同类;法术攻击则能一击必杀,但可能消耗更多体力。游戏包含N种不同怪兽,JYY需要找出消灭所有怪兽的最低体力消耗。通过分析,发现这是一个存在环的最短路径问题,不适合直接用DP解决,而是采用SPFA算法。初始所有点入队,不断更新节点的最小花费,最后dis[1]即为答案。虽然这种方法的时间复杂度较高。

description

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

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


analysis

  • 可以设f[i]f[i]f[i]表示彻底杀死iii怪兽的最小值,则f[i]=min(k[i],s[i]+∑j∈sonf[j])f[i]=min(k[i],s[i]+\sum_{j\in son}f[j])f[i]=min(k[i],s[i]+jsonf[j])

  • 但是这种转移可以成环,所以不能用DPDPDP做,而这种类似松弛操作可以用SPFASPFASPFA来做

  • 由于初始不知道从哪个点开始更新会最优,初始所有点都入队

  • dis[i]dis[i]dis[i]表示最小花费,一开始也不知道物理攻击以后如何转化,先都赋值成k[i]k[i]k[i]

  • 更新nownownow即为枚举所有nownownow的儿子,拿∑j∈sondis[j]\sum_{j\in son}dis[j]jsondis[j]来更新dis[now]dis[now]dis[now]

  • 对于队首nownownow的更新,它的变动可能影响到所有经平A后可以得到nownownow的点

  • 所以建反向边,若队首更新成功,把可以得到队首的点全部再入队

  • dis[1]dis[1]dis[1]即为答案。这个做法时间复杂度应该是很大的……


code

#pragma GCC optimize("O3")
#pragma G++ optimize("O3")
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#define MAXN 200005
#define MAXM 1000005
#define ll long long
#define reg register ll
#define fo(i,a,b) for (reg i=a;i<=b;++i)
#define fd(i,a,b) for (reg i=a;i>=b;--i)
#define rep(i,a) for (reg i=las[a];i;i=nex[i])
#define rep1(i,a) for (reg i=las1[a];i;i=nex1[i])

using namespace std;

ll las[MAXM],nex[MAXM],tov[MAXM];
ll las1[MAXM],nex1[MAXM],tov1[MAXM];
ll dis[MAXN],phy[MAXN];
ll n,tot,tot1;
bool bz[MAXN];
queue<ll>q;

inline ll read()
{
	ll x=0,f=1;char ch=getchar();
	while (ch<'0' || '9'<ch){if (ch=='-')f=-1;ch=getchar();}
	while ('0'<=ch && ch<='9')x=x*10+ch-'0',ch=getchar();
	return x*f;
}
inline ll max(ll x,ll y){return x>y?x:y;}
inline ll min(ll x,ll y){return x<y?x:y;}
inline void link(ll x,ll y){nex[++tot]=las[x],las[x]=tot,tov[tot]=y;}
inline void link1(ll x,ll y){nex1[++tot1]=las1[x],las1[x]=tot1,tov1[tot1]=y;}
int main()
{
	freopen("knight.in","r",stdin);
	freopen("knight.out","w",stdout);
	n=read();
	fo(i,1,n)
	{
		phy[i]=read(),dis[i]=read();ll tmp=read(),x;
		while (tmp--)x=read(),link(i,x),link1(x,i);
	}
	fo(i,1,n)q.push(i);
	while (!q.empty())
	{
		ll now=q.front(),tmp=0;
		q.pop(),bz[now]=1;
		rep(i,now)tmp+=dis[tov[i]];
		if (phy[now]+tmp<dis[now])
		{
			dis[now]=phy[now]+tmp;
			rep1(i,now)if (bz[tov1[i]])q.push(tov1[i]),bz[tov1[i]]=0;
		}
	}
	printf("%lld\n",dis[1]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值