p3387

· · 题解

主要是看看关键路径的求法


允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。

。。。
一看便是缩点了(这不就是模板吗);
缩点后一张有向无环图是不存在“回头走”的; 
也自然只走了一次 

分析

缩点模板,蒟蒻用的是tarjan和spfa做的;
题目目的: 
缩点:将一张有向有环的图,变为一张有向无环图,便于操作;
求关键路径:一张AOV图上的最长路(可能不止一条); 
分析:
缩点用tarjan,楼上大佬讲得很好了,这里不加累述;
求关键路径:这是一个无根树,要有一个源点和汇点;
或者每个点都当做根跑一遍;
为了简单我这里用了后者 
但题目木有,so我们得自己加一个超级源点和超级汇点;
为了简便我这里使用了SPFA(还是挺快的);
每条边的权值是是每个出发点的点值; 

这里我只给出第二种,用源点和汇点的留给大家思考

直接放代码了


#include<bits/stdc++.h>// 万能头 
using namespace std; 
const int inf = 500100;// 边数 
const int INF = 100100;// 点数 
int head[inf];// 原图的 
bool vis[INF];// 是否在栈or队列 
int sd[INF];// 缩点后的点值 
int color[INF];// 原点属于一个强联通分量的一个颜色 
int v[inf];// 原点值 
int cnt = 0,n,m,total=0;
stack<int> q;// tarjan的栈 
struct Edge{
    int to;
    int next;
}edge[inf];// 原边 
struct EDGE{
    int to;
    int next;
}e[inf];// 现边 
int cctv=0;
int tarjan_head[inf];
void add_edge(int x,int y)//加现边 
{
    cctv++;
    e[cctv].next=tarjan_head[x];
    e[cctv].to=y;
    tarjan_head[x]=cctv;
}
void add(int x,int y)// 加原边 
{
    cnt++;
    edge[cnt].next=head[x];
    edge[cnt].to=y;
    head[x]=cnt;
}
int dfn[INF];// dfs到的时间 
int low[INF];// 可以追溯到的最小的dfn 
int tim=1;// dfs的时间 
void tarjan(int x)
{
    dfn[x]=low[x]=tim;
    tim++;
    vis[x]=true;
    q.push(x);
    for(int i=head[x];i;i=edge[i].next)
    {
        int u=edge[i].to;
        if(!dfn[u])
        {
            tarjan(u);
            low[x]=min(low[x],low[u]);//tarjan板子 
        } 
        else if(vis[u]) low[x]=min(low[x],dfn[u]);//在栈中 
    }
    if(low[x]==dfn[x]) 
    {
        //找到了这个分量的代表元素了 
        total++;
        int y;
        do{
            //出栈 
            y=q.top();
            q.pop();
            vis[y]=false;
            color[y]=total;
            sd[total]+=v[y];
        }while(x!=y);
    }
}
int dist[inf];
int spfa(int x)
{
    int sum=0;
    memset(dist,-0x3f,sizeof(dist));
    memset(vis,false,sizeof(vis));//为了节约空间,再用一次吧 
    queue<int> Q;//spfa的板子 
    Q.push(x);
    vis[x]=true;
    dist[x]=0;
    while(!Q.empty())
    {
        x=Q.front();
        Q.pop();// 出队 
        vis[x]=false;
        sum=max(sum,dist[x]+sd[x]);//要判断现点上的值和,边上加上的值 
        for(int i=tarjan_head[x];i;i=e[i].next)
        {
            int y=e[i].to;
            if(dist[y]<dist[x]+sd[x])
            dist[y]=dist[x]+sd[x];// 更新 
            if(vis[y]) continue;
            Q.push(y);
        }
    }
    //cout<<"sum :"<<sum<<endl;
    return sum;
}
int main()
{
    cin>>n>>m;// 输入 
    for(int i=1;i<=n;i++)
    cin>>v[i];// 输入 
    for(int i=1;i<=m;i++)
    {
        int a,b;
        cin>>a>>b;
        add(a,b);// 原图的边 
    }
    for(int i=1;i<=n;i++)
    {
        if(!dfn[i])
        {
            tarjan(i);// 跑tarjan 
        } 
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=head[i];j;j=edge[j].next)
        {
            int xx=edge[j].to;// 原边,若不是一个强联通分量的,再在现图上加边 
            if(color[i]!=color[xx])
            add_edge(color[i],color[xx]);// 加边 
        }
    }
    int ans=0;
    for(int i=1;i<=total;i++)
    {
        ans=max(spfa(i),ans);//跑SPFA 
    }
    cout<<ans<<endl;
    return 0;
}