传送门

好题!考察了对于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();
} 
View Code
01-23 08:50