洛谷 P3387 tarjan缩点(模板题)

本人第一次做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);  {最后输出便利到的最大值}
}

 

转载于:https://www.cnblogs.com/Dilemma/p/9304363.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值