P3627 [APIO2009] 抢掠计划

这题是学长给我们讲的。当时我和 ham 已经糊出来了正解,但学长问谁会的时候仍然不敢举手。或许是讨论被听到了,学长说:

"有的同学啊已经知道了正解但是不敢举手啊"。

尴尬尴尬,,,


传送门

形式化地:

给定一张有 \(n\) 个节点,\(m\) 条边的有向图,每个点上有一个权值 \(w_i\),从 \(1\) 号节点出发,每一次可以获得当前节点的权值,但无法获得经过过的节点权值。随后给定 \(P\) 个特殊节点,你的结束应当在这 \(P\) 个特殊节点之一。

\(1\le n,m\le 5\times 10^5\)

Sol

注意到可能有环,而且访问过的节点不在具有价值,首先不可能用 \(vis\) 数组标记,这样不好跑最短(长)路。

考虑到访问过的节点不在具有价值,也就意味着可以用 tarjan 缩点后在 DAG 上操作。若是需要访问某个强连通分量,那么将其缩成一个点同样是具有正确性的。

当然的,当我们将一个强连通分量缩成一个点后,这个“合并点”应当维护:

  • 权值之和(sum)

  • 是否有特殊节点(flag)

在当 tarjan 后的 DAG 上就可以乱搞啦!

接下来显然就是一个最长路,由于没有环,所以可以跑 DP 或者 SPFA。

因为 DP 更加稳定,不会被卡,学长推荐,所以我选择了已经死了的 SPFA。(那我不管,我过了)

SPFA 转为负边权跑最短路,直接跑最长路都是可行的做法,我就直接跑一遍最长路啦~

code:

#include<bits/stdc++.h>
int n,m;
std::vector<int>e[500086];
std::vector<int>f[500086];
int w[500086];
int awa[500086];
bool flag[500086];
int s,p;
int ans;
int dfn[500086];
int low[500086];
int cl[500086];
int point;
int cnt;
bool vis[500086];
int dis[500086];
std::stack<int>st;
void add(int x,int y){
    e[x].push_back(y);
}
inline void tarjan(int u){//模版
    low[u]=dfn[u]=++point;
    vis[u]=true;
    st.push(u);
    for(int i:e[u])
        if(!dfn[i]){
            tarjan(i);
            low[u]=std::min(low[u],low[i]);
        }
        else
            if(vis[i])
                low[u]=std::min(low[u],low[i]);
    if(low[u]==dfn[u]){
        cnt++;
        int qwq;
        do{
            qwq=st.top();
            st.pop();
            cl[qwq]=cnt;
            vis[qwq]=false;
        }while(qwq!=u);
    }
    return;
}
std::queue<int>q;
inline void spfa(){
    dis[cl[s]]=awa[cl[s]];
    q.push(cl[s]);
    while(not q.empty()){
        int x=q.front();
        q.pop();
        for(int i:f[x])
            if(dis[x]+awa[i]>dis[i]){//只要改这里就好啦~
                dis[i]=dis[x]+awa[i];
                q.push(i);
            }
    }
    return;
}
int main(){
    std::cin>>n>>m;
    for(int i=1;i<=m;i++){
        int x,y;
        std::cin>>x>>y;
        add(x,y);//加边!加边!加边!(无端
    }
    for(int i=1;i<=n;i++)
        std::cin>>w[i];
    std::cin>>s>>p;

    for(int i=1;i<=p;i++){
        int x;
        std::cin>>x;
        flag[x]=true;
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i])
            tarjan(i);//开始缩点
    for(int i=1;i<=n;i++)
        for(int j:e[i])
            if(cl[i]!=cl[j])
                f[cl[i]].push_back(cl[j]);//建新图
    for(int i=1;i<=n;i++){
        awa[cl[i]]+=w[i];//awa其实是sum
        if(flag[i])
            flag[cl[i]]=true;//flag就是是否有特殊节点
    }
    spfa();//最长路
    for(int i=1;i<=cnt;i++)
        if(flag[i])//要停在特殊节点
            ans=std::max(ans,dis[i]);//选择最长路
    std::cout<<ans;
    return 0;//撒花!
}
posted @ 2024-07-25 20:36  立花廿七  阅读(8)  评论(2编辑  收藏  举报