好题!考察了对于SPFA的深刻理解。
对于每个怪,我们要么花费魔攻代价,要么花费普攻代价+消灭其儿子的代价。
看似像个一直递归下去的做法,仿佛可以用dp?
但是图中可能会存在环,就会有后效性。
假如我们用一个队列保存当前还需要消灭的怪。
我们每次提出队首,要么魔攻,要么普攻然后又压入一堆新出现的怪。直到队为空。
感觉这个过程有点像SPFA。
SPFA是对于每次那些还可能松弛更新的边进行松弛,直到无法操作(队空)就保证了最短路。
对于这道题,我们好像也可以每次看能不能再更新消灭怪的最小花费,直到不能再更新。
即使有正环也不会影响最短路。
如果连边,显然不是很好操作。不如考虑直接模拟SPFA的操作过程。
dis[i]存的不是起点1到它的最短路,我们定义dis[i]为消灭i及其产物的最小花费。
我们考虑从某一个怪被魔攻(一定会有一些怪被魔攻,不然产生怪无止境),其对于父亲的dis会做出贡献。
或者说一个怪不一定是魔攻,但消灭其及其产物的最小代价是dis[i],这个dis[i]会对父亲的dis做出贡献。
把父亲的魔攻代价和普攻代价+消灭其儿子的代价相比进行更新操作。
如果父亲被更新了,说明父亲的父亲也还有可能更新,于是把父亲的父亲再次入队。
所以把dis[i]一开始就设为这个怪被魔攻的代价。
我们保存一个点的儿子和它可以更新的父亲。
跑一遍SPFA,最后输出dis[1]即可。
#include<bits/stdc++.h> #define LL long long #define N 200003 #define M 1000003 using namespace std; LL read() { LL x=0,f=1;char s=getchar(); while(s<‘0‘||s>‘9‘){if(s==‘-‘)f=-1;s=getchar();} while(s>=‘0‘&&s<=‘9‘){x=x*10+s-‘0‘;s=getchar();} return x*f; } struct EDEG{ int nextt,to; }w[M]; vector<int>fa[N]; LL s[N],dis[N]; int vis[N],head[N]; int tot=0,n; void add(int a,int b) { tot++; w[tot].nextt=head[a]; w[tot].to=b; head[a]=tot; } queue<int>q; void SPFA() { while(!q.empty())q.pop(); for(int i=1;i<=n;++i)vis[i]=1,q.push(i); while(!q.empty()) { int x=q.front();q.pop(); vis[x]=0; LL tmp=s[x]; for(int i=head[x];i;i=w[i].nextt) { int v=w[i].to; tmp+=dis[v]; } if(tmp>=dis[x])continue; dis[x]=tmp; for(int i=0;i<fa[x].size();++i) { int f=fa[x][i]; if(vis[f])continue; vis[f]=1;q.push(f); } } printf("%lld\n",dis[1]); } int main() { n=read(); for(int i=1;i<=n;++i) { s[i]=read(),dis[i]=read(); int num=read(); while(num--) { int x=read(); add(i,x);fa[x].push_back(i); } } SPFA(); }
原文地址:https://www.cnblogs.com/yyys-/p/11697472.html
时间: 2024-10-08 20:44:34