码迷,mamicode.com
首页 > 其他好文 > 详细

机房测试11:信息拦截 (tarjan缩点+正反拓扑)

时间:2019-10-11 20:22:15      阅读:78      评论:0      收藏:0      [点我收藏+]

标签:print   min   flag   out   答案   oid   class   false   isp   

题目:

技术图片

 

技术图片

 

 

 分析:

这道题真的毒瘤,思想很简单,但是细节很多。。

题意:找到从1~n的必经点(每条信息都能获取),且不在一个点数>=2 的强连通分量中(恰好获取一次)。

先将有向图缩点,转换成一张有向无环图。

然后对缩点后的图进行正反拓扑,求出必经点。

再看必经点是否在一个点数>=2的强联通分量中。

正反拓扑过程:

 fs[u]表示从起点到u的路径条数,ft[u]表示从终点到u的路径条数

由乘法原理可知,若一个点满足:fs[u]*ft[u]==fs[t]

这个点是必经点。

细节:

1. 对于存在自环的点,一定不能为最后答案,所以要用self标记一下。

2. 要求用按照遍历顺序输出,要用拓扑序记录一下遍历顺序。

3. 缩点从1出发,而不是将整张图的强联通分量都找出来!!

4. 拓扑的起点为1,终点为n。

5. 缩点后的连边可能会有重边,但这并不影响答案。

6. 路径数会很大,对大质数取模即可(可能会冲突)。

 

技术图片
#include<bits/stdc++.h>
using namespace std;
#define ri register int
#define N 1000005
#define ll long long
const ll mod=1e9+7;
int read()
{
    int x=0,fl=1; char ch=getchar();
    while(ch<0||ch>9) { if(ch==-) fl=-1; ch=getchar(); }
    while(ch>=0&&ch<=9) x=x*10+ch-0,ch=getchar();
    return x*fl;
}
int n,m,tot=0,cnt=0,Ti=0,head[N],nex[N],to[N],fa[N],self[N];
int dfn[N],low[N],stk[N],top=0,bel[N],ru1[N],ru2[N],flag[N],siz[N];
ll fs[N],ft[N];
vector<int> e1[N];
vector<int> e2[N];
vector<int> ans;
vector<int> scc[N];
void init()
{
    tot=Ti=cnt=top=0; ans.clear();
    for(ri i=1;i<=max(tot,n);++i) 
    self[i]=siz[i]=dfn[i]=low[i]=stk[i]=bel[i]=ru1[i]=ru2[i]=head[i]=fs[i]=ft[i]=flag[i]=0,e1[i].clear(),e2[i].clear(),scc[i].clear();
}
void add(int a,int b) { if(a==b) { self[a]=true; return ; } to[++tot]=b; nex[tot]=head[a]; head[a]=tot; }
void tarjan(int x)
{
    dfn[x]=low[x]=++Ti;
    if(x==n) siz[x]=1;
    stk[++top]=x; flag[x]=true;
    for(ri i=head[x];i;i=nex[i]){
        int v=to[i];
        if(!dfn[v]){
            tarjan(v);
            low[x]=min(low[x],low[v]);
        }
        else if(flag[v]) low[x]=min(low[x],dfn[v]);
        siz[x]|=siz[v];
    }
    if(dfn[x] == low[x]){
        cnt++;
        do{
            int xx=stk[top];
            scc[cnt].push_back(xx); flag[xx]=false;
            bel[xx]=cnt;
        }while(stk[top--]!=x);
    }
}
queue<int> q;
int tup[N];
void topu()
{
    int num=0;
    q.push(bel[1]); fs[bel[1]]=1;
    while(!q.empty()){
        int u=q.front(); q.pop();
        tup[++num]=u;
        for(ri i=0;i<e1[u].size();++i){
            int v=e1[u][i];
            fs[v]=fs[v]+fs[u];
            if(fs[v]>=mod) fs[v]%=mod;
            if(--ru1[v]==0) q.push(v);
        }
    }
    q.push(bel[n]); ft[bel[n]]=1;
    while(!q.empty()){
        int u=q.front(); q.pop();
        for(ri i=0;i<e2[u].size();++i){
            int v=e2[u][i];
            ft[v]=ft[v]+ft[u];
            if(ft[v]>=mod) ft[v]%=mod;
            if(--ru2[v]==0) q.push(v);
        }
    }
    for(ri i=1;i<=num;++i){
        int u=tup[i];
        if( fs[u]*ft[u]%mod==fs[bel[n]] && scc[u].size()==1 && !self[scc[u][0]]) ans.push_back(scc[u][0]);
    }
    printf("%d\n",ans.size());
    for(ri i=0;i<ans.size();++i) printf("%d ",ans[i]);
    printf("\n");
}
int main()
{
    freopen("i.in","r",stdin);
    freopen("i.out","w",stdout);
    int T=read();
    while(T--){
        init();
        n=read(), m=read();
        int a,b;
        for(ri i=1;i<=m;++i) a=read(),b=read(),add(a,b);
        tarjan(1);
        for(ri i=1;i<=n;++i)
         if(dfn[i] && siz[i])
          for(ri j=head[i];j;j=nex[j]){
             int v=to[j]; 
             if(bel[i]==bel[v] || !siz[v]) continue;//chong bian
             e1[bel[i]].push_back(bel[v]);
             ru1[bel[v]]++;
             e2[bel[v]].push_back(bel[i]);
             ru2[bel[i]]++;
        }
        topu();
    }
}
/*
6
4 3
2 4
1 3
3 2

2 2
1 2
2 1

3 1
2 3

4 4
1 2
2 4
3 4
1 3

6 6
1 3
3 2
2 1
1 4
5 4
4 6

7 9
1 2
2 4
2 3
4 3
3 6
6 3
3 5
6 5
5 7
*/
View Code

 

机房测试11:信息拦截 (tarjan缩点+正反拓扑)

标签:print   min   flag   out   答案   oid   class   false   isp   

原文地址:https://www.cnblogs.com/mowanying/p/11650896.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!