为什么 Tarjan 用手写栈就可以不开 O2 过,但是 STL 栈不开 O2 就过不了?
刚开始写这道题的时候,由于本人十分喜欢用 STL 而十分厌恶手写的栈,就用 STL 中栈写了一个 Tarjan,代码如下:
void Tarjan(int u){
low[u]=dfn[u]=++tim;//初次访问
st.push(u);
vis[u]=true;
for(int v:g[u]){
if(dfn[v]==0){//不曾访问
Tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v]){
low[u]=min(low[u],low[v]);
}
}
if(dfn[u]==low[u]){//自成一个强连通分量
int v;
do{
p[u]+=p[v];
v=st.top();
st.pop();
vis[v]=false;
scc[v]=u;
}while(u!=v);
}
}
其中,p 数组表示的是一个强连通分量的总点权,我用一个结点编号代表一个强连通分量,也即scc[u]==u
的时候,u 这个结点代表了一个强连通分量。
当时开着 O2 也是过去了,也没怎么在意。后来我二刷这道题的时候,发现不开 O2 这道题就过不去了,这怎么行呢?于是我调试了三天三夜也没调试出来,放弃了。这是完整代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e4+10;
int n,m;
int low[MAXN],dfn[MAXN],tim=0;//dfn[i]表示初次访问i点的时间戳
bool vis[MAXN];
vector<int> g[MAXN];
stack<int> st;//访问到某个点u的时候,栈中存储u的祖先和已经访问过的可以到达u的祖先的点
int scc[MAXN];
int p[MAXN];//连通块的总点权
int in_d[MAXN];
queue<int> q;
int dis[MAXN];
struct EDGE{
int startpoint;
int endpoint;
}edge[(MAXN<<3)+(MAXN<<1)];
void Tarjan(int u){
low[u]=dfn[u]=++tim;//初次访问
st.push(u);
vis[u]=true;
for(int v:g[u]){
if(dfn[v]==0){//不曾访问
Tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v]){
low[u]=min(low[u],low[v]);
}
}
if(dfn[u]==low[u]){//自成一个强连通分量
int v;
do{
p[u]+=p[v];
v=st.top();
st.pop();
vis[v]=false;
scc[v]=u;
}while(u!=v);
}
}
int tp(){
for(int i=1;i<=n;++i){
if(scc[i]==i&&in_d[i]==0){
q.push(i);
dis[i]=p[i];
}
}
while(q.size()){
int u=q.front();
q.pop();
for(int v:g[u]){
dis[v]=max(dis[v],dis[u]+p[v]);
--in_d[v];
if(in_d[v]==0){
q.push(v);
}
}
}
int ans=-1;
for(int i=1;i<=n;++i){
ans=max(ans,dis[i]);
}
return ans;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%d",&p[i]);
}
for(int i=1,u,v;i<=m;++i){
scanf("%d%d",&u,&v);
g[u].push_back(v);
edge[i].startpoint=u;
edge[i].endpoint=v;
}
for(int i=1;i<=n;++i){
if(dfn[i]==0){
Tarjan(i);
}
}
for(int i=1;i<=n;++i){
g[i].clear();
}
for(int i=1;i<=m;++i){
int u=scc[edge[i].startpoint],v=scc[edge[i].endpoint];
if(u!=v){
g[u].push_back(v);
++in_d[v];
}
}
printf("%d\n",tp());
return 0;
}
几天后我想到,没有一个 O2 和无 O2 的 Tarjan 板子怎么行呢?于是我被迫重写了一次 Tarjan,完整代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int MAXN=1e4+10;
int stk[MAXN],dfn[MAXN],low[MAXN],tp=0,tim=0;
bool vis[MAXN];
vector<int> g[MAXN];
int scc_cnt=0,scc[MAXN];
int n,m;
int in_d[MAXN];
int dis[MAXN],p[MAXN],f[MAXN];
queue<int> q;
struct EDGE{
int startpoint;
int endpoint;
}edge[(MAXN<<3)+(MAXN<<1)];
void Tarjan(int u){
low[u]=dfn[u]=++tim;
stk[++tp]=u;
vis[u]=true;
for(int v:g[u]){
if(!dfn[v]){
Tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v]){
low[u]=min(low[u],low[v]);
}
}
if(dfn[u]==low[u]){
int v;
++scc_cnt;
do{
v=stk[tp--];
vis[v]=false;
scc[v]=scc_cnt;
p[scc_cnt]+=f[v];
}while(u!=v);
}
}
int Topo(){
for(int i=1;i<=scc_cnt;++i){
if(in_d[i]==0){
q.push(i);
dis[i]=p[i];
}
}
while(q.size()){
int u=q.front();
q.pop();
for(int v:g[u]){
dis[v]=max(dis[v],dis[u]+p[v]);
--in_d[v];
if(in_d[v]==0){
q.push(v);
}
}
}
int ans=0;
for(int i=1;i<=scc_cnt;++i){
ans=max(ans,dis[i]);
}
return ans;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%d",&f[i]);
}
for(int i=1,u,v;i<=m;++i){
scanf("%d%d",&u,&v);
g[u].push_back(v);
edge[i].startpoint=u,edge[i].endpoint=v;
}
for(int i=1;i<=n;++i){
if(!dfn[i]){
Tarjan(i);
}
}
for(int i=1;i<=n;++i){
g[i].clear();
}
for(int i=1;i<=m;++i){
int u=scc[edge[i].startpoint],v=scc[edge[i].endpoint];
if(u!=v){
g[u].push_back(v);
++in_d[v];
}
}
printf("%d",Topo());
}
然后发现无论有没有 O2 这份代码都能够过。
在此求助各位大佬,这是为什么?可以看到两份代码唯一的区别就是手写栈和 STL 栈。