本人第一次做tarjan,这类水题花了2:30小时才调好,一是因为刚转c++不习惯,总会把==打成=,找了好久的错。然后接着一直40分,是发现,now=next(now)打在了括号的外面,真的是蒟蒻。
好了进入正题了。这题我觉得是tarjan缩点然后再将不再一个强联通分量中的点重新连边,然后直接spfa遍历(按理来说floyd直接各个强联通分量3方爆扫好像也是可以的)。这是我的解法。之前还有很多dalao的解法,好像很多用的都是dp,但是我没有想到。。。。
{详细解释请见代码中的注释}
#include<vector>
#include<cstdio>
#include<cstring>
#define MAXN (1000001) {我觉得这个一个好习惯,因为这可以防止因为手贱而导致的mle或空间开小了}
#define ll long long
#define INF (0x7f7f7f7f)
#define max(a,b) (((a)>(b)) ? (a):(b))
#define min(a,b) (((a)>(b)) ? (b):(a)){手打的max,min要比cmath中的会快}
using namespace std;
int q[MAXN],dis[MAXN];
int get[MAXN];
int top,size,len,k,dc,maxx;
int a[MAXN],head[MAXN],next[MAXN],color[MAXN],dfn[MAXN],low[MAXN],stack[MAXN],f[MAXN],x1[MAXN],y1[MAXN],rd[MAXN];
bool visit[MAXN],instack[MAXN],vis[MAXN];
void add(int x,int y ){
++top;
get[top]=y;
next[top]=head[x];
head[x]=top; {邻接表不用多说}
}
void SPFA(int x){
memset(vis,0,sizeof(vis));
memset(dis,0,sizeof(dis));
int h=0,t=1; {刚开始将起点进队开始更新}
q[1]=x; dis[x]=f[x];{因为是点的遍历,所以刚开始要把自己到自己的权值设成点权,不然后面就会少一个起点的值}
while (h<t){
++h;
int x=q[h];
vis[x]=0; {因为已经弹出所以标记为不在队列中}
int now=head[x];
while (now>0){
int g=get[now];
if (dis[x]+f[g]>dis[g]) {
dis[g]=dis[x]+f[g];
if (not vis[g]) { {如果不在队列中那么进队}
t++;
q[t]=g;
vis[g]=1;
}
}
now=next[now];
}
}
for (int i=1;i<=dc;++i)
maxx=max(maxx,dis[i]); {用当前几点到所有点的距离更新答案maxx值}
}
void tarjan(int x){
++k; int u=x;
dfn[x]=low[x]=k; visit[x]=true; instack[x]=true;++top; stack[top]=x;
int now=head[x];
do {
int v=get[now];
if (visit[v]==0) {
tarjan(v); {如果目标点不在栈中那么继续往下搜}
low[u]=min(low[u],low[v]); {用你儿子的low值去更新你的low值,因为你的low值必定要小于等于你儿子的low值}
} else
if (instack[v]) low[u]=min(dfn[v],low[u]);{如果目标点已经在栈中了,那么你就可以直接用你儿子的dfn值也就是时间戳来更新你的low值}
now=next[now];{邻接表便利}
} while(now>0);
if (dfn[x]==low[x]) { {如果满足条件,就说明找到了一个强联通分量,那么就开始弹栈,直到弹到当前这一个点为止}
++dc;
while (stack[top+1]!=x){ {因为当前这一个点也要弹出,所以是top+1,不然的话就会少弹一个}
color[stack[top]]=dc;{染色}
f[dc]+=a[stack[top]];{统计每一个颜色的点的总权值}
instack[stack[top]]=0;{弹栈}
top--; {将栈顶指针-1}
}
}
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i) scanf("%d",&a[i]);
for (int i=1;i<=m;++i){
int x,y;
scanf("%d%d",&x1[i],&y1[i]);
add(x1[i],y1[i]);
}
top=0;
for (int i=1;i<=n;++i)if (not visit[i]) tarjan(i);
memset(head,0,sizeof(head));
memset(next,0,sizeof(next));
memset(get,0,sizeof(get));
top=0;
for (int i=1;i<=m;++i)
if (color[x1[i]]!=color[y1[i]]) { {如果这条边的两个点不在同一个强联通分量中,那么连边}
add(color[x1[i]],color[y1[i]]); {连}
rd[color[y1[i]]]++; {将目标点的rd+1,以便后面处理}
}
for (int i=1;i<=dc;++i){
if (rd[i]==0) {因为rd不为0的点肯定会被别的点走到,所以只需要便利rd为0的边,可以节省时间} SPFA(i);
}
printf("%d",maxx); {最后输出便利到的最大值}
}