这道题前面的思路和其他题解一样,都是tarjan缩点。然后是要求金币的最大值,我用的是记忆化搜索,用jiyi[i]来表示从编号为i的联通块出发,最多可以抢到多少钱,搜索时如果遇到已经搜过的点(知道从那个点开始最多能抢多少钱)就直接return。因为记忆化搜索每个点只会遍历一次,所以记忆化搜索的复杂度应该是O(n)。
另外我的码风有点奇怪,先解释一下:stk[tk]分别是栈和栈的指针(tarjan用),num表示联通块个数,tim时间戳,shuyu[i]表示点i属于哪个联通块,he[]表示某个联通块内总共有多少钱,vis[]表示一个点是否在栈中,jiuba[]表示某个点是否有酒吧,suojb[]表示缩点后的连通块中是否有酒吧。bian[],head[],ecnt都是缩点前图中的边,如果后面有后缀x,则表示缩点后新图中的边;
#include<bits/stdc++.h>
using namespace std;
const int N=500005;
int n,m,ecnt,s,p,tk,num,tim,ecntx,ans;
int qian[N],head[N],dfn[N],low[N],stk[N],shuyu[N],he[N],headx[N],jiyi[N];
bool vis[N],jiuba[N],suojb[N];
struct aaa
{
int to,nxt;
}bian[N],bianx[N];
inline int read()//标准快读
{
int x=0;bool f=0;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return f?-x:x;
}
inline void add(int a,int b)//链式前向星加边;
{
bian[++ecnt].to=b;
bian[ecnt].nxt=head[a];
head[a]=ecnt;
}
inline void addx(int a,int b)//缩点后联通块与联通块加边;
{
bianx[++ecntx].to=b;
bianx[ecntx].nxt=headx[a];
headx[a]=ecntx;
}
void tarjan(int u)//标准tarjan
{
dfn[u]=low[u]=++tim;
stk[++tk]=u;vis[u]=1;
for(int i=head[u];i;i=bian[i].nxt)
{
int v=bian[i].to;
if(!dfn[v])
tarjan(v),low[u]=min(low[u],low[v]);
else if(vis[v])low[u]=min(low[u],low[v]);
}
if(dfn[u]==low[u])
{
num++;
while(stk[tk]!=u)
{
shuyu[stk[tk]]=num;//记录点属于的联通块;
vis[stk[tk]]=0;
stk[tk]=0;tk--;
}
shuyu[stk[tk]]=num;//此时栈顶还有一个点在此联通块中,再执行一次
vis[stk[tk]]=0;
stk[tk]=0;tk--;
}
}
void suo()//缩点;
{
for(int i=1;i<=n;i++)
{
he[shuyu[i]]+=qian[i];//更新每个联通块内的总钱数
if(jiuba[i])suojb[shuyu[i]]=1;更新联通块内是否有酒吧
for(int j=head[i];j;j=bian[j].nxt)
{
int v=bian[j].to;
if(shuyu[i]!=shuyu[v])
addx(shuyu[i],shuyu[v]);//如果起点和终点不在同一个联通块,就在这两个联通块之间加边;
}
}
}
int dfs(int dian)//缩了点,新图为有向无环图,此时才能dfs
{
if(jiyi[dian])return jiyi[dian];//如果这个联通块已经被搜索过,即我们知道从这个点开始的最大钱数,就直接返回;
if(headx[dian]==0&&suojb[dian]==1)//如果这个联通块出度为0并且此处有酒吧,就可以在此处停止,返回这个联通块的总钱数;
{
jiyi[dian]=he[dian];
return he[dian];
}
if(headx[dian]==0&&suojb[dian]==0)return 0;//如果这个联通块出度为0但是没有酒吧,就不能停止在此处,所以它对答案的贡献是0;
for(int i=headx[dian];i;i=bianx[i].nxt)
{
jiyi[dian]=max(jiyi[dian],dfs(bianx[i].to));//一个联通块的多个出度中取一个最大的;
}
if(jiyi[dian]==0&&suojb[dian]==0)return 0;
//注意:我们此时少考虑了一种情况,如果一条路径上一连串的点都没有酒吧,那么劫匪是不可以在这条路径上停下的,但如果没了这句话,程序还是会走这条路;加上这句话,如果后面的点没有酒吧 并且 此处也没有酒吧,那么它对答案的贡献是0,直接return 0;
jiyi[dian]+=he[dian];//把自己的点权加进记忆化数组中;
return jiyi[dian];//返回从这点开始的最大钱数;
}
int main()
{
int a,b;
n=read();m=read();
for(int i=1;i<=m;i++)
{
a=read();b=read();
add(a,b);
//有个奇怪的错误,不能写add(read(),read());否则会出错,我也不知道为什么;
}
for(int i=1;i<=n;i++)
qian[i]=read();
s=read();p=read();
for(int i=1;i<=p;i++)
jiuba[read()]=1;
for(int i=1;i<=n;i++)
if(!dfn[i])
tarjan(i);
suo();
ans=dfs(shuyu[s]);
cout<<ans;
return 0;
}