返回顶部

【Tarjan】缩点

题意

给定一个 n 个点 m 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。

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

 

img


经验

这题使我被迫改变变量命名习惯,因为各种计数变量太多了。。。 不过可读性很高,起码只看代码是能看明白逻辑的

 

要注意的是,本题思路就是把所有强连通分量当成一个节点,算有向图的最大经过点权

也就是说(我)存在一个疑问:

一个scc向另一个scc连了多于一条边时,难道入度的增加不会影响拓扑排序的结果吗??

我单步调试代码,之后发现了玄妙之处,

拓扑cnt的增加在有点被队列删除时进行,

也就是说:一个点到另一个点多出来的入度会在vector搜边时减去,所以即使有重复的入度,也会在vector操作中被化解影响,也就不影响其他点的关系了

下面是6个小时思考加独立书写的代码,步骤划分十分清楚

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#define il inline
using namespace std;
const int maxn=1e4+11;
const int maxm=1e5+11;
inline int read() {
    register int x=0,f=1;
    register char c=getchar();
    while(c<'0'||c>'9') {
        if(c=='-') f=-1;
        c=getchar();
    }
    while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x*f;
}
/////////////////////////////////
//题意数据
int n,m,val[maxn]; 
/////////////////////////////////
//链式前向星建边 
struct edge{
    int from,to,nxt;
}e[maxm]; 
int h[maxn],Qxxcnt;
il void addedge(int x,int y)
{
    Qxxcnt++;
    e[Qxxcnt].from=x,e[Qxxcnt].to=y,e[Qxxcnt].nxt=h[x];
    h[x]=Qxxcnt;
}
///////////////////////////////////////////
//Tarjan
int timer,STACKcnt,SCCcnt; 
int dfn[maxn],_low[maxn],_stack[maxn]; 
bool vis[maxn];
int color[maxn];//每个点所属的强连通编号 
int SCCval[maxn];
il void Tarjan(int x)
{
    dfn[x]=_low[x]=++timer;
    ++STACKcnt; _stack[STACKcnt]=x; vis[x]=1;
    for(int i=h[x]; i ;i=e[i].nxt)
    {
        int v=e[i].to;
        if(!dfn[v])
        {
            Tarjan(v);
            _low[x]=min(_low[x],_low[v]);
        }
        else if(vis[v])
        {
            _low[x]=min(_low[x],dfn[v]);
        } 
    }
    if(dfn[x]==_low[x])
    {
        SCCcnt++;
        while(1)
        {
            color[_stack[STACKcnt]]=SCCcnt;//记录该点的SCC 
            SCCval[SCCcnt]+=val[_stack[STACKcnt]] ;//将该点的值贡献给SCC 
            vis[_stack[STACKcnt]]=0; STACKcnt--;//出栈 
            
            if(x==_stack[STACKcnt+1]) break;//当x刚好被请出去的时候 
        }
    }
}
///////////////////////////////////////////////////
//拓扑排序
vector<int > G[maxn];
vector<int > rdr[maxn];//已知终点,记录一条边的起点 
queue<int > q;
int in[maxn];//入度 
int ans[maxn];//拓扑顺序
int TOPOcnt; 
​
void build_init()//建立拓扑排序初始化 
{
    for(int i=1;i<=Qxxcnt;i++)//按边读取关系 
    {
        int x=color[e[i].from], y=color[e[i].to];//该边两点所属的SCC都求出来,判断关系建边
        if(x!=y)
        {
            G[x].push_back(y); in[y]++;
            rdr[y].push_back(x); 
        } 
    }
} 
​
void topo()
{
for(int i=1;i<=SCCcnt;i++)
    {
if(in[i]==0) q.push(i);
    }
while(!q.empty())
    {
int u=q.front();
ans[++TOPOcnt]=u; q.pop();
for(int i=0;i<G[u].size(); i++)
    {
int v=G[u][i];
in[v]--;
if(in[v]==0) q.push(v);
    }
    }

} 
///////////////////////////////////////////////////////////////////////////
//动态规划
int f[maxn];
void dp_work()
{
for(int i=1;i<=SCCcnt;i++)
    {
int w=ans[i];//拓扑顺序,w为当前SCC编号 
f[w]=SCCval[w];
for(int j=0;j<rdr[w].size();++j) 
    {
int fa=rdr[w][j];
f[w]=max(f[w],f[fa]+SCCval[w]);
    }
    }
int final_ans=-1;
for(int i=1;i<=SCCcnt;i++)
    {
final_ans=max(final_ans,f[i]);
    }
printf("%d\n",final_ans);
}
///////////////////////////////
//主函数 
int main()
{
//freopen("data.in","r",stdin); 
n=read(),m=read();
for(int i=1;i<=n;i++) val[i]=read();
for(int i=1;i<=m;i++) {
int x,y; x=read(),y=read();
addedge(x,y); 
    }
///////////
for(int i=1;i<=n;i++)
if(!dfn[i]) Tarjan(i);
///////////
build_init();
topo();
///////////
dp_work();
return 0; 
}```

 



posted @ 2022-06-03 15:34  魔幻世界魔幻人生  阅读(24)  评论(0)    收藏  举报