CEOI2012 Network

题目大意:给出一个有向图,有N个结点,M条边。
如果从p到q有一条有向边组成的路径,则称结点p可以到达结点q。
输入数据保证初始状态时,每对可达结点之间存在唯一路径。
图中有一个中心点R,它可以到达所有结点。
任务:
(1)求出每个顶点能够到达的顶点数量(包括自身)
(2)最少加多少条边,使得所有顶点之间均可以相互到达,且是唯一路径。
样例

11 12 3 
3 2 
2 1 
2 4 
4 5 
4 6 
6 2 
6 7 
3 8 
8 9 
9 10 
9 11 
10 8 

样例说明
样例输出

1 6 11 6 1 6 1 4 4 4 1
5 
1 3 
5 4 
7 6 
11 9 
8 3 

首先呢,说明一下:当存在两组无交集的边都能使u到达v时,u到v的路径不唯一
这是我目前觉得最精准的解释了。。。
由于初始图路径保证唯一,所以这个图必然只有树边和返祖边。
A任务:
在tarjan的DFS过程中记录一下本节点只通过树边能到的节点数量,再记录一下最多只通过一条返祖边能到达最高的(即DFN最小)节点。再次DFS,找到该节点能到的最高的节点。答案就是该节点能到的最高的节点的能到达的节点的数量(为什么DFN最小就最优?此处留坑
至于为什么要两次DFS?是因为第一次DFS时,当你更新时,通过多条反向边的必定是还未走过的,所以需要2次DFS。此处继续留坑

B任务:
说白了,就是问你加哪些边可以使原图变成一堆简单环,且原图的每条边只经过一次。
由于加的边数要最小,显然从叶子节点尽可能往上走是最优的。
我们设想从每个出度为0的点开始出发,沿着树边的反向边走,遍历出要加入它的环的边(拓扑的思想),直到马上要走的下一个边已被走过,这时候我们就到了能走的最高点了,然后就在出发点和这个点间连一条边。
但在这之前,由于图上已经存在着一些环,我们要给它们打上已被走过的标记。
还有,走的时候记着要让被走到的点的初度-1,如果初度为0则入队
此处以1为例,我们从1出发,走到它的父亲2,然后给这条边打上标记。再走到3,这时候我们已经到头了,所以1和3连一条边
再以5为例,我们从5出发,走到4,4-6这条边已经在环上了,不能走了所以我们在5和4间连一条边
再以2为例,2-3这条边已经被1走过了,所以不能走,所以2和2之间连一条边,所以不连边
好了,上程序

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#include<queue>
using namespace std;
const int N=100005,M=1000005;
int head[N],fa[N],cnt;//邻接表用
struct node{
    int u,v,next;
}edge[M];
bool vis[N];//在dfs1中表示该点是否被访问,dfs2中表示该点连向父亲的边是否已访问
int dfn[N],low[N];
bool instack[N];
int tot;//dfn时间
int son[N],high[N],highest[N];//A任务的那几个东西
int scccnt,scc[N];//储存强连通分量
int e[N];//出度
int outc,out[N][2];//输出
stack<int> S;
queue<int> Q;
int n,m,r;
void add_edge(int u,int v){
    cnt++;
    edge[cnt].u=u;
    edge[cnt].v=v;
    edge[cnt].next=head[u];
    head[u]=cnt;
}
void dfs(int u){
    low[u]=dfn[u]=++tot;
    son[u]=1;
    instack[u]=1;
    high[u]=u;
    S.push(u);
    for(int i=head[u];i;i=edge[i].next){
        int v=edge[i].v;
        if(!dfn[v]){
            dfs(v);
            son[u]+=son[v];
            fa[v]=u;
            e[u]++;
            low[u]=min(low[u],low[v]);
            if(dfn[high[u]]>dfn[high[v]])
                high[u]=high[v];
        }
        else{
            if(dfn[high[u]]>dfn[high[v]])
                high[u]=high[v];
        }
        if(instack[v])
            low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u]){
        scccnt++;
        int x;
        do{
            x=S.top();
            S.pop();
            scc[x]=scccnt;
            instack[x]=0;
        }while(x!=u);
    }
}
void dfs1(int u){
    vis[u]=1;
    int v;
    if(high[u]==u)
        highest[u]=u;
    else
        highest[u]=highest[high[u]];
    for(int i=head[u];i;i=edge[i].next){
        v=edge[i].v;
        if(!vis[v])
            dfs1(v);
    }
}
void dfs2(){
    vis[r]=1;
    for(int i=1;i<=n;i++){
        if(e[i]==0)
            Q.push(i);
        if(scc[i]==scc[fa[i]]){
            e[fa[i]]--;
            if(e[fa[i]]==0)
                Q.push(fa[i]);
            vis[i]=1;
        }
    }
    while(!Q.empty()){
        int x=Q.front();
        Q.pop();
        int x1=x;
        while(!vis[x1]){
            vis[x1]=1;
            e[x1]--;
            x1=fa[x1];
        }
        if(x==x1)
            continue ;
        e[x1]--;
        if(e[x1]==0)
            Q.push(x1);
        outc++;
        out[outc][0]=x;
        out[outc][1]=x1;
    }
}
int main()
{
    //freopen("network.in","r",stdin);
    //freopen("network.out","w",stdout);
    scanf("%d%d%d",&n,&m,&r);
    for(int i=1;i<=m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        add_edge(a,b);
    }
    dfs(r);
    tot=0;
    dfs1(r);
    printf("%d",son[highest[1]]);
    for(int i=2;i<=n;i++)
        printf(" %d",son[highest[i]]);
    puts("");
    for(int i=1;i<=n;i++)
        if(!scc[i])
            scc[i]=++scccnt;
    memset(vis,0,sizeof vis);
    dfs2();
    printf("%d\n",outc);
    for(int i=1;i<=outc;i++)
        printf("%d %d\n",out[i][0],out[i][1]);
}

顺带附一个某大佬的题解
http://blog.csdn.net/qq_34454069/article/details/77926089

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值