P4042 [AHOI2014/JSOI2014]骑士游戏

题目描述

在这个游戏中,JYY一共有两种攻击方式,一种是普通攻击,一种是法术攻击。两种攻击方式都会消耗JYY一

些体力。采用普通攻击进攻怪兽并不能把怪兽彻底杀死,怪兽的尸体可以变出其他一些新的怪兽,注意一个

怪兽可能经过若干次普通攻击后变回一个或更多同样的怪兽;而采用法术攻击则可以彻底将一个怪兽杀死。

当然了,一般来说,相比普通攻击,法术攻击会消耗更多的体力值(但由于游戏系统bug,并不保证这一点)。

游戏世界中一共有 \(N\) 种不同的怪兽,分别由1到 \(N\) 编号,现在1号怪兽入侵村庄了,JYY想知道,

最少花费多少体力值才能将所有村庄中的怪兽全部杀死呢?

说明/提示

首先用消耗4点体力用普通攻击,然后出现的怪兽编号是2,2和3。花费10点体力用法术攻击杀死两个

编号为2的怪兽。剩下3号怪兽花费1点体力进行普通攻击。此时村庄里的怪兽编号是2和4。

最后花费11点体力用法术攻击将这两只怪兽彻底杀死。一共花费的体力是4+5+5+1+5+6=26。

对于所有数据 \(2 \le N \le 2 \times 10^5,1 \le R_i,\sum R_i \le 10^6,1 \le K_i,S_i \le 5 \times 10^{14}\)

这道题,我们很容易就想到转移方程

\(f_i\) 表示消灭第 \(i\) 只怪兽的最小代价

那么转移方程就可以写成 \(f_i = min(k_i,s_i+\displaystyle\sum f[R[i][j])\)

可是这会出现环也就是有后效性的dp,我们就需要一种方法来处理这个。

当后面的 \(k_i\) 变小时,那么前面的 \(f_i\)也会发生变化。

这不就类似于spfa的松弛操作吗?

于是,我们就可以每次求完一个点的\(f\)值后,如果他的\(f_i\)变小,那我们需要将能变成它的怪兽在重新入队

在进行更新,直到所有的点都不能更新为止。

但一般的最短路都是有起点和终点的,这个题我们无法确定从那个点开始更优。

所以,我们干脆一开始把所有点都加入队列中,都进行更新。

以上就是我们spfa处理有后效性dp的思路

Code

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<queue>
using namespace std;
#define int long long//不开long long 见祖宗
const int N = 2e5+10;
int n,tot,cnt,num,x;
int s[N],dis[N],head[N],hed[N];
bool vis[N];
inline int read()
{
	int s = 0, w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s =s * 10+ch - '0'; ch = getchar();}
	return s * w;
}
queue<int> q;
struct node{int to,net;} e[1000010],e2[1000010];
void add(int x,int y)
{
	e[++tot].to = y;
	e[tot].net = head[x];
	head[x] = tot;
}
void Add(int x,int y)
{
	e2[++cnt].to = y;
	e2[cnt].net = hed[x];
	hed[x] = cnt;
}
signed main()
{
	n = read();
	for(int i = 1; i <= n; i++)
	{
	    s[i] = read(); dis[i] = read(); num = read();//一开始杀死一只怪兽的代价为魔法攻击消耗的体力值
	    for(int j = 1; j <= num; j++)
	    {
	    	x = read(); add(i,x); Add(x,i);
	    } 
	}
	for(int i = 1; i <= n; i++) q.push(i), vis[i] = 1;//一开始把所有的点都进入队列中
        while(!q.empty())
        {
    	      int t = q.front(); q.pop(); vis[t] = 0; int tmp = s[t];
    	      for(int i = head[t]; i; i = e[i].net)//计算把他变成其他怪兽,并将它们消灭的最小代价
    	      {
    		 int to = e[i].to;
    		 tmp += dis[to];
    	      }
    	      if(tmp <= dis[t])//如果杀死他的代价变小了,我们就要把能变成他的怪兽都重新入队在进行次更新
    	      {
    		  dis[t] = tmp;
    		  for(int i = hed[t]; i; i = e2[i].net)
    		  {
    		      int to = e2[i].to;
    		      if(!vis[to])
    		      {
    		         q.push(to); 
    			 vis[to] = 1;
    		      }
    		   }
    	        }
          }
//    for(int i = 1; i <= n; i++) cout<<dis[i]<<endl;
      printf("%lld\n",dis[1]);
    return 0;
}

ENDING

原文地址:https://www.cnblogs.com/genshy/p/13527120.html