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;//撒花!
}