【洛谷2921】 [USACO08DEC]在农场万圣节(Tarjan+记忆化搜索)

版权声明:本文为作者原创,若转载请注明地址。 https://blog.csdn.net/chenxiaoran666/article/details/81974413

点此看题面
大致题意: N 个点,第 i 个点有一个值 N e x t i ,规定了你到达编号为 i 的节点后下一个要到的节点。每当你到达一个已经经过的节点后,就会结束遍历。问你从编号为 1 N 的每一个节点出发所能经过的点数。


对于只有环的的情况

假设这张图上只有环,那么这道题应该就很简单了,直接 T a r j a n 求出每一个节点所在环的环长即可(出度入度皆为1的情况下,强连通分量只有环)。

Tarjan详见博客用Tarjan来实现强连通分量缩点


考虑如何处理链

好吧,可惜这张图上不仅有环,还有链。
那么对于链怎么处理呢?
多画画图,就可以发现,一条链最后肯定连向一个环,也就是说,对于链的情况,答案就是从当前节点到达某一个环的距离再加上这个环的环长。


具体实现

既然这样,我们还是先 T a r j a n 求出每一个强连通分量的大小,不难发现,每一个强连通分量中的所有节点最终答案都是一样的。
因此,对于每一个强连通分量,我们可以 d f s 搜出它的答案(不停往下搜,每经过一个强连通分量就将 a n s 加上这个强连通分量的大小)。
但是,只要数据够强,这样的方法还是会被卡成 O ( n 2 )
怎么办呢?
我们可以考虑记忆化。
对于每一个强连通分量,可以用 a n s i 直接记录这个强连通分量中每一个节点的答案,以后访问到这个强连通分量,直接返回 a n s i 即可,就不用继续往下搜了。
这样一来,复杂度就是 O ( n ) 了。


代码

#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define abs(x) ((x)<0?-(x):(x))
#define LL long long
#define ull unsigned long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define tc() (A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++)
#define pc(ch) (pp_<100000?pp[pp_++]=(ch):(fwrite(pp,1,100000,stdout),pp[(pp_=0)++]=(ch)))
#define add(x,y) (e[++ee].to=y,e[ee].nxt=lnk[x],lnk[x]=ee)
#define nadd(x,y) (ne[++nee].to=y,ne[nee].nxt=nlnk[x],nlnk[x]=nee)
#define N 100000
int pp_=0;char ff[100000],*A=ff,*B=ff,pp[100000];
using namespace std;
int n,m,ee=0,nee=0,cnt=0,d=0,top=0,lnk[N+5],nlnk[N+5],dfn[N+5],low[N+5],col[N+5],vis[N+5],Stack[N+5],tot[N+5],ans[N+5];
struct edge
{
    int to,nxt;
}e[2*N+5],ne[2*N+5];
queue<int> q;
inline void read(int &x)
{
    x=0;int f=1;static char ch;
    while(!isdigit(ch=tc())) f=ch^'-'?1:-1;
    while(x=(x<<3)+(x<<1)+ch-48,isdigit(ch=tc()));
    x*=f;
}
inline void write(int x)
{
    if(x<0) pc('-'),x=-x;
    if(x>9) write(x/10);
    pc(x%10+'0');
}
inline void Tarjan(int x)//Tarjan预处理,并求出每个强连通分量的大小
{
    register int i;
    dfn[x]=low[x]=++d,vis[Stack[++top]=x]=1;
    for(i=lnk[x];i;i=e[i].nxt)
    {
        if(!dfn[e[i].to]) Tarjan(e[i].to),low[x]=min(low[x],low[e[i].to]);
        else if(vis[e[i].to]) low[x]=min(low[x],low[e[i].to]);
    }
    if(!(dfn[x]^low[x]))
    {
        vis[x]=0,tot[col[x]=++cnt]=1;//用tot统计该强连通分量大小
        while(Stack[top]^x) vis[Stack[top]]=0,col[Stack[top--]]=cnt,++tot[cnt];
        --top;
    }
}                                   
inline void dfs(int x)//记忆化搜索
{
    register int i;
    for(ans[x]=tot[x],i=nlnk[x];i;i=ne[i].nxt)//枚举它能到达的强连通分量(其实就一个)
    {
        if(!ans[ne[i].to]) dfs(ne[i].to);//如果这个强连通分量没有访问过,就求出这个强连通分量的答案
        ans[x]+=ans[ne[i].to];//将当前强连通分量的答案加上它能到达的强连通分量的答案
    }
}           
int main()
{
    register int i,j,x,y;
    for(read(n),i=1;i<=n;++i) read(x),add(i,x); 
    for(i=1;i<=n;++i) if(!dfn[i]) Tarjan(i);
    for(i=1;i<=n;++i)
        for(j=lnk[i];j;j=e[j].nxt)
            if(col[i]^col[e[j].to]) nadd(col[i],col[e[j].to]);
    for(i=1;i<=n;++i)//枚举每一个节点,求出并输出答案
    {
        if(!ans[col[i]]) dfs(col[i]);//如果当前节点所在的强连通分量没有被访问过,就访问该强连通分量并求出答案
        write(ans[col[i]]),pc('\n');//输出
    }
    return fwrite(pp,1,pp_,stdout),0;
}

后记

做完才发现,因为每个点入度出度皆为1,所以我这个做法真的太麻烦了,完全可以不 T a r j a n 直接记忆化搜索,说不定还跑得更快。

猜你喜欢

转载自blog.csdn.net/chenxiaoran666/article/details/81974413