JZOJ3674. 【JSOI2014】骑士游戏(knight)

题目大意

一共有n个怪兽,你能对每个怪兽进行两种攻击方式,假如是进行普通攻击,那么该怪兽死后会分解出若干个怪兽(可能是自己),如果是进行法术攻击那么该怪兽将被斩草除根。普通攻击和法术攻击都有其对应的体力值,试求当编号为1的怪兽入侵时,杀死该怪兽所需的最小体力。

分析

这是一道不错的题目,我们可以将这种普攻打死后生成若干个怪兽看做一个有自环的有向图。设 f [ u ] f[u] f[u]表示彻底消灭怪兽 u u u的最小体力值。那么转移就是 f [ u ] = m i n ( k [ u ] , s [ u ] + s u m ( s o n ) ) f[u] = min(k[u],s[u] + sum(son)) f[u]=min(k[u],s[u]+sum(son))其中 k [ u ] k[u] k[u]表示法术攻击, s [ u ] s[u] s[u]表示普通攻击, s u m ( s o n ) sum(son) sum(son)表示用普攻将该怪兽杀死后产生的怪兽杀掉所需的体力值。那么怎么处理出 f [ 1 ] f[1] f[1]就比较显然了。先将 k [ u ] k[u] k[u]从小到大排序,很显然从当前最小的 k [ u ] k[u] k[u]直接用法术攻击杀掉是最优的,然后将与它相连的点的入度减一,若某点的入度为零或该点直接用法术攻击杀死已经比当前未处理完的 s [ u ] + s u m ( s o n ) s[u] +sum(son) s[u]+sum(son)小了,便将该点入队(类似处理拓扑的方法)。

Code

#include 
#include 
#include 
#define ll long long
using namespace std;

const int N = 2e5 + 10;
struct Edge{
	int to,next;
} f[N * 10];
struct Node{
	ll a;
	int p;
} g[N];
int n,rd[N],cnt,head[N];
ll ans[N],s[N],k[N];
queue <int> q;

ll min(ll a,ll b) {return a < b ? a : b;}

void add(int u,int v)
{
	f[++ cnt].to = v;
	f[cnt].next = head[u];
	head[u] = cnt;
}

bool cmp(Node a,Node b) {return a.a < b.a;}

int main()
{
	freopen("knight.in","r",stdin);
	freopen("knight.out","w",stdout);
	scanf("%d",&n);
	for (int i = 1,m; i <= n; i ++)
	{
		scanf("%lld%lld%d",&s[i],&k[i],&m);
		rd[i] = m,g[i].a = k[i],g[i].p = i;
		for (int j = 1,v; j <= m; j ++) scanf("%d",&v),add(v,i);
	}
	sort(g + 1,g + 1 + n,cmp);
	for (int i = 1,u; i <= n; i ++)
		if (!ans[u = g[i].p])
		{
			ans[u] = 1ll * k[u];
			q.push(u);
			while (!q.empty())
			{
				u = q.front();
				q.pop();
				for (int i = head[u],v; i; i = f[i].next)
				{
					v = f[i].to;
					if (ans[v]) continue;
					rd[v] --,s[v] += ans[u];
					if (k[v] <= s[v]) ans[v] = k[v],q.push(v); else 
					if (!rd[v]) ans[v] = s[v],q.push(v);
				}
			}
			if (ans[1]) break;
		}
	printf("%lld\n",ans[1]);
	return 0;
}

你可能感兴趣的:(题解)