noip2015 信息传递 强连通块

时隔好久,重新把那些题找出来,写一写了,毕竟实力还很有限,得学。

一开始,最大的疑惑还是怕一个环里有另外一个环(毕竟经验不丰富),后来看到了这段话

T2 信息传递

大意:在一个只有n条有向边的图中,每个结点出度为1,求一个包含节点数最少的环。

分析:因为只有n条边并且每个点都有且仅有一条边连出去,所以只可能存在简单环,不会出现那种8字形的环套环。

证明:每个点有两种情况:1.不在任何一个环中 这样的点可以直接忽略掉,看成是有n-1个点,n-1条边来证明就行了。2.在某个环中。那么如果我们现在有了一个圈圈形状的环,大小为k,那么每一个点都应该入度出度都为1,这样的环只存在一个回路,也就是一个简单环,如果存在多个回路,那么应该存在某个点的入度>1,但是又要保证别的点出度至少为1,所以出度之和应该是>k的,但入度之和=k,无法满足,所以不存在某个环存在两条或以上数量的回路。证毕。

满足了这样的性质,tarjan或者O(n)扫描都是可以解决本题的。

恍然大悟,既然弱,就多积累吧!

不过,也据说“这题我觉得,真的,画图是关键(我记得我以前的竞赛老师王老师经常说的一件事情就是数形结合),多画图,多模拟,就很容易找到需要解决问题的方法。”。觉得也挺有道理的,发现自己很多时候都是引用别人现成的,属于自己的本质的想法会比较少。

另附,网上好像还有其他的做法,比如并查集法,还有一种把入度为0的点删去后,再dfs查找的。先给出后者的代码。

即用类似拓扑的方式把所有入度为0的点踢掉,然后可以保证剩下的每个点都在一个有向环中,同时这个图特殊的建法好像可以保证任意两个环之间没有公共点...用on的时间扫一遍就可以了

 1 #include<cstdio>
 2 #include<iostream>
 3 #define rep(i,j,k) for(int i = j; i <= k; i++)
 4 #define maxn 200009
 5 using namespace std;
 6 int next[maxn] = {0};
 7 bool used[maxn] = {0};
 8 int num[maxn] = {0}, ans = 0xfffffff;
 9
10 int read()
11 {
12     int s = 0, t = 1;
13     char c = getchar();
14     while( !isdigit(c) ){
15         if( c == ‘-‘ ) t = -1;
16         c = getchar();
17     }
18     while( isdigit(c) ){
19         s = s * 10 + c - ‘0‘;
20         c = getchar();
21     }
22     return s * t;
23 }
24
25 void dfs(int x)
26 {
27     used[x] = 1;
28     num[next[x]]--;
29     if( !num[next[x]] ) dfs(next[x]);
30 }
31
32 void search(int x,int num)
33 {
34     used[x] = 1;
35     if( !used[next[x]] ){
36         search(next[x],num+1);
37     }
38     else if( num != 1 ) ans = min(num,ans);
39 }
40
41 int main()
42 {
43     int n = read();
44     rep(i,1,n){
45         next[i] = read();
46         num[next[i]]++;
47     }
48     rep(i,1,n){
49         if( !num[i] && !used[i] ) dfs( i );
50     }
51     rep(i,1,n){
52         if( !used[i] )
53         search(i,1);
54     }
55     if( ans > n ) cout<<0<<endl;
56     else cout<<ans<<endl;
57     return 0;
58 }

接下来,写求强连通块的算法。使用两次dfs的。这个算法在codevs上跑的时间比上面的多一些(但这个求完强连通分量后,还能得到原图的拓扑排序)。

 1 #include<cstdio>
 2 #include<iostream>
 3 #include<vector>
 4 #include<stack>
 5 #include<cstring>
 6 #define maxn 200009
 7 #define rep(i,j,k) for(int i = j; i <= k; i++)
 8 using namespace std;
 9
10 bool used[maxn] = {0};
11 int next[maxn] = {0};
12 vector<int> q[maxn];
13 stack<int> s;
14 int ans = 0xfffffff;
15
16 int read()
17 {
18     int s = 0, t = 1;
19     char c = getchar();
20     while( !isdigit(c) ){
21         if( c == ‘-‘ ) t = -1;
22         c = getchar();
23     }
24     while( isdigit(c) ){
25         s = s * 10 + c - ‘0‘;
26         c = getchar();
27     }
28     return s * t;
29 }
30
31 void dfs(int i)
32 {
33     used[i] = 1;
34     if( !used[next[i]] ) dfs(next[i]);
35     s.push(i); //注意到最后才把这个元素压入栈。
36 }
37
38 int search(int x)
39 {
40     used[x] = 1;
41     int s = q[x].size();
42     int ans = 1;
43     rep(i,0,s-1){
44         if( !used[q[x][i]] )
45         ans += search(q[x][i]);
46     }
47     return ans;
48 }
49
50 int main()
51 {
52     int n = read();
53     rep(i,1,n){
54         next[i] = read();
55         q[next[i]].push_back(i);
56     }
57     rep(i,1,n){
58         if( !used[i] ) dfs(i);
59     }
60     memset(used,0,sizeof(used));
61     rep(i,1,n){
62         int m = s.top(), k = 0; s.pop();
63         if( !used[m] ){
64             k = search(m);  //注意当 k = 1 时应舍去。
65             if( k <= 1 ) continue;
66             else ans = min(k,ans);
67         }
68     }
69     cout<<ans<<endl;
70     return 0;
71 }

接下来是tarjan算法求强连通块。这个算法也要比上面第二种快一点。

#include<cstdio>
#include<iostream>
#include<stack>
#define maxn 200009
#define rep(i,j,k) for(int i = j; i <= k; i++)
using namespace std;

int pre[maxn] = {0};
int low[maxn] = {0};
int next[maxn] = {0};
int sccno[maxn] = {0};
bool used[maxn] = {0};
int ans = 0xfffffff, cnt = 0;
int sccno_cnt = 0;
stack<int> s;

int read()
{
    int s = 0, t = 1;
    char c = getchar();
    while( !isdigit(c) ){
        if( c == ‘-‘ ) t = -1;
        c = getchar();
    }
    while( isdigit(c) ){
        s = s * 10 + c - ‘0‘;
        c = getchar();
    }
    return s * t;
}

void dfs(int now)
{
    pre[now] = low[now] = ++cnt;
    used[now] = 1;
    s.push(now);
    if( used[next[now]] ){
        low[now] = min(pre[next[now]],low[now]);
    }
    else if( !sccno[next[now]] ) {
        dfs(next[now]);
        low[now] = min(low[now],low[next[now]]);
    }
    if( pre[now] == low[now] ){
        int num = 0;
        int t = s.top();
        sccno_cnt++;
        while( t != now ){
            sccno[t] = sccno_cnt;
            t = s.top();
            s.pop();
            num++;
        }
        if( num <= 1 ) return;
        else ans = min(ans,num);
    }
}

int main()
{
    int n = read();
    rep(i,1,n){
        next[i] = read();
    }
    rep(i,1,n){
        if( !used[i] ){
            dfs(i);
        }
    }
    cout<<ans<<endl;
    return 0;
}

就这样,求强连通分量的两种常见算法都打了,而且还有针对此题的一种算法。加油!最后说一句,如果把STL的栈改成手打的一个数组,效率会提高一些;可能有几毫秒的提升;不过我也不敢下断言,毕竟比较弱。

总结,根据codevs和vijos的数据显示,第一个算法跑的时间最短,效率最高(两个网站都能AC),其次是第三个算法(codevs能AC,vijos最后一个点就判了编译错误,其他的点都A了);最差的是第二个算法(codevs能AC,但vijos上只能过两个点,也有可能是我水平有限,无法进一步优化)。

加油,明天会更好。

时间: 2024-12-05 21:44:37

noip2015 信息传递 强连通块的相关文章

[NOIP2015]信息传递

[NOIP2015]信息传递[问题描述]有??个同学(编号为1到??)正在玩一个信息传递的游戏.在游戏里每人都有一个固定的信息传递对象,其中,编号为?? 的同学的信息传递对象是编号为????的同学.游戏开始时,每人都只知道自己的生日.之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息,但是每人只会把信息告诉一个人,即自己的信息传递对象).当有人从别人口中得知自己的生日时,游戏结束.请问该游戏一共可以进行几轮?[输入格式]输入文件名为m

2105. [NOIP2015] 信息传递

★☆   输入文件:2015message.in   输出文件:2015message.out   简单对比 时间限制:1 s   内存限制:256 MB [题目描述] 有n个同学(编号为1到n)正在玩一个信息传递的游戏.在游戏里每人都有一个固定的信息传递对象,其中,编号为i的同学的信息传递对象是编号为Ti同学. 游戏开始时,每人都只知道自己的生日.之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息,但是每人只会把信息告诉一个人,即自

图论例题1——NOIP2015信息传递

题目描述 有n个同学(编号为1到n)正在玩一个信息传递的游戏.在游戏里每人都有一个固定的信息传递对象,其中,编号为i的同学的信息传递对象是编号为Ti同学. 游戏开始时,每人都只知道自己的生日.之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息,但是每人只会把信息告诉一个人,即自己的信息传递对象).当有人从别人口中得知自己的生日时,游戏结束.请问该游戏一共可以进行几轮? 输入输出格式 输入格式: 输入共2行. 第1行包含1个正整数n表示

cogs luogu 2105. [NOIP2015] 信息传递

★☆   输入文件:2015message.in   输出文件:2015message.out   简单对比 时间限制:1 s   内存限制:256 MB [题目描述] 有n个同学(编号为1到n)正在玩一个信息传递的游戏.在游戏里每人都有一个固定的信息传递对象,其中,编号为i的同学的信息传递对象是编号为Ti同学. 游戏开始时,每人都只知道自己的生日.之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息,但是每人只会把信息告诉一个人,即自

P2661 信息传递 强连通分量

题目链接: http://www.luogu.org/problem/show?pid=2661 题解: 这题求最小的单向环. 可因为每个节点初度为1,所以所有的强联通分量都只能是单向环. 所以就是有向图强连通分量的模板题. #include<iostream> #include<cstdio> #include<vector> #include<stack> #include<algorithm> #include<cstring>

洛谷P2661 信息传递 类似tarjan 强连通 灌水

洛谷P2661 信息传递类似tarjan 强连通 灌水 题意 求一个特殊的图的最小环 这个图 有一个性质 每个点只有一条出边 这样满足一个性质,一张图只有 一个环,以及别的连向他们的边都是一些连向 或者 间接连向这个环的树枝 这些树枝一定不会连成环,因为 每个点只有一条出边,而不可能有两条,所以只要把这些树枝边都删掉,然后再类似tarjan一样跑一遍灌水就行了 先说一下思路,这整道题就是给你几个带枝叶的环要你求最短环而已,于是在输入的时候可以把图中每个点的入度记录下来,然后再删除那些入度为0的点

信息传递 NOIP2015 day1 T2

题文: 有n个同学(编号为1到n)正在玩一个信息传递的游戏.在游戏里每人都有一个固定的信息传递对象,其中,编号为i的同学的信息传递对象是编号为Ti同学. 游戏开始时,每人都只知道自己的生日.之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息,但是每人只会把信息告诉一个人,即自己的信息传递对象).当有人从别人口中得知自己的生日时,游戏结束.请问该游戏一共可以进行几轮? 输入共2行. 第1行包含1个正整数n表示n个人. 第2行包含n个用空

【 NOIP2015 DAY1 T2 信息传递】带权并查集

题目描述 有n个同学(编号为1到n)正在玩一个信息传递的游戏.在游戏里每人都有一个固定的信息传递对象,其中,编号为i的同学的信息传递对象是编号为Ti同学. 游戏开始时,每人都只知道自己的生日.之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息,但是每人只会把信息告诉一个人,即自己的信息传递对象).当有人从别人口中得知自己的生日时,游戏结束.请问该游戏一共可以进行几轮? 输入输出格式 输入格式: 输入共2行. 第1行包含1个正整数n表示

洛谷P2661 信息传递==coedevs4511 信息传递 NOIP2015 day1 T2

P2661 信息传递 题目描述 有n个同学(编号为1到n)正在玩一个信息传递的游戏.在游戏里每人都有一个固定的信息传递对象,其中,编号为i的同学的信息传递对象是编号为Ti同学. 游戏开始时,每人都只知道自己的生日.之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息,但是每人只会把信息告诉一个人,即自己的信息传递对象).当有人从别人口中得知自己的生日时,游戏结束.请问该游戏一共可以进行几轮? 输入输出格式 输入格式: 输入共2行. 第1