NOIP 2015 提高组 Day1 信息传递

求最小的环的长度。 没思路大概这已经卡死一小半人。

我的起初的想复杂了。 后来觉得:这道题真简单:n个点,n-1条边,如果没有环的话,这是一棵树。
这些题目应该不可能考察这些非常规的高难度的算法把。

求强联分量

起初的想法,,然后在处理。
这些题目应该不可能考察这些非常规的高难度的算法把。
题外话:网上看了一个好东西:有人提出了这样的算法。显然该无向图存在连通分支。对每个连通分支单独进行考虑。

对于一个连通图,任取一个它的生成树(有算法可以完成这项操作),连通分支中除过这些树枝剩下的边我们称作弦,每一条弦对应该连通图的一个基本回路,无向图的所有回路都可以表示成这些基本回路的直和(边集的并,去掉公共边),所以最小回路一定在这些基本回路里产生。我们现在把这些弦一条一条往里加就可以了。加入一条弦,立即产生一个基本回路,计算此回路边数并记录在一个数组里。去掉此弦,加入另一条弦,会有另一个回路产生,计算边数,保存。把所有的弦都试一遍,得到一个数组,取最小元素对应的弦,把弦加进生成树,就得到最小回路。这样的算法很节省空间。可以得到所有最小回路。
貌似合理:其实不行,看反例:
 -  fence1(1)
 / \ fence2(1),fence3(1)
 --- fence4(3)
/    \ fence5(1),fence6(1)
------fence7(6)
\____/fence 8(1),fence9(1),fence10(1)
可以发现,存在一种最小生成树为:

  -         fence1(1)
 /          fence2(1)
/    \      fence5(1),fence6(1)
\____/      fence8(1),fence9(1),fence10(1)

根据好友“川菜“思路:强联通分量。
#include <iostream>
//#define debug
#include <bits/stdc++.h>
using namespace std;

const int MM=200001;
int  T[MM];
vector<int>  ReT[MM];
bool used[MM];
vector<int> xu;
int huan[MM];

void dfs(int v){
    used[v]=true;
    int next= T[v];
    if( next !=-1 && !used[next] )  {
            used[next]=true;
            dfs( next ) ;
    }
    xu.push_back(v);
}

void dfs2(int v,int k){
    huan[k]++;
    #ifdef debug
    cout <<"v :"<<v <<"  ||"<<"huan:"<<k << " "<< huan[k] <<"  ";
    #endif
    used[v]=true;
    for(int i=0;i<ReT[v].size();i++){
      int next=ReT[v][i];
      if( next !=-1 && !used[next] )  {
            used[next]=true;
            dfs2( next , k ) ;
      }
    }

}

int main()
{
    //freopen("message1.in","r",stdin);
    //freopen("mesaage.out","w",stdout);
    int N; cin>>N;
    memset(T,-1,sizeof(T));
    for(int i=1;i<=N;i++){
        cin>>T[i];
        //ReT[ T[i] ] = i;//这里错了。 反向以后,可能有好几个儿子。
        ReT[ T[i] ].push_back(i);
    }

   #ifdef debug
    for(int i=1;i<=N;i++){
       cout << "i:" <<i <<"next:"<< T[i]<<";" ;
    }
    #endif // debug

    memset(used, 0 ,sizeof(used));
    for(int i=1;i<=N;i++){
        if( !used[i] ) {
             dfs(i);
        }
    }

    memset(used,0,sizeof(used));
    memset(huan,0,sizeof(huan));

    #ifdef debug
     for(int i=0;i<xu.size();i++){
            cout << "xu"<< i << ":" << xu[i]<<"  ";
            cout <<endl;
    }
    #endif // debug

     int k=0;
     for(int i=xu.size()-1;i>=0;i--){
        if( !used[ xu[i] ] ) {
             dfs2( xu[i], ++k );
        }
    }

    int ans=MM;
    for(int i=1;i<=k;i++){
       if( huan[i]!=1) ans = min( huan[i] , ans );
    }
    cout << ans;
    return 0;
}


思路二 拓扑 + DFS

  先拓扑,删掉入度为零的点,剩下的就是环了。 然后DFS
记下每次访问到的节点的时间戳,如果再次访问到该节点,将此时的时间戳,减去上次的时间戳。这应该是正解。
有环的话,只有一个环。 只有一个环吗? 我想错了!!
先扫描一遍结点,将入度为0的结点入队; 然后从队列里依次取出这些结点,删除它的出边,删除该结点(做删除标记),修改出边结点的入度,如果其入度为0,则入队。 重复上述过程直至队列为空,拓扑过程结束。 剩下的结点一定构成环。
、在扫描一遍结点,遇到入度不为0的结点,就以它为 起点 dfs,查环的长度
 
感觉思路对的,luogu上值通过了10组数据。  写后感: 刚开始 拓扑排序不熟练,通过了6组数据, 还有四组内存超过了。
#include <iostream>

#include <bits/stdc++.h>
using namespace std;

const int MM=200001;
int  T[MM];
int  rudu[MM];
int S;
int ans=MM;
int N;
bool used[MM];

//要优化,否则很多数据通不过

/* 1、先扫描一遍结点,将入度为0的结点入队;
 * 然后从队列里依次取出这些结点,删除它的出边,删除该结点(做删除标记),修改出边结点的入度,如果其入度为0,则入队。
 * 重复上述过程直至队列为空,拓扑过程结束。 剩下的结点一定构成环。
 *
 * 2、在扫描一遍结点,遇到入度不为0的结点,就以它为起点查环的长度,同时删除该结点(做删除标记)。
 *
 */

void tp(){
    queue<int> q;
    for(int v=0; v<=N; v++){
        if( rudu[v]==0 ) {
                  q.push(v);
        }
    }

    while( !q.empty()){
        int f=q.front() ;  q.pop();
        int next = T[f];
        T[f]=-1;
        if(next!=-1)
        {   rudu[next]--;
            if( rudu[next]==0 )  {q.push( next ); }
        }
    }
}

void dfs(int v,int step){
    int next= T[v];
    if( next == S) {
        ans = min( ans, step ) ;
        return;
    }
    if( next !=-1 )  {
            dfs( T[v] , step+1) ;
            used[next]=true;
    }
}

int main()
{
    //freopen("message1.in","r",stdin);
   // freopen("mesaage.out","w",stdout);
     cin>>N;
    memset(rudu,0,sizeof(rudu));
    memset(used, 0 ,sizeof(used));

    for(int i=1;i<=N;i++){
        cin>>T[i];
        rudu[ T[i] ]++;
    }

    tp();
     //这里可以不用深度搜索,直接while语句往下搜索,遇到起点后,统计一下环的长度,也方便
    memset(used, 0 ,sizeof(used));
    for(int i=1;i<=N;i++){
        if( rudu[i]==1 && !used[i] ) {
                S = i;
                used[i]=true;
                dfs(i, 1);
        }
    }

    cout << ans;
    return 0;
}


附: 数据2: 

输入:

50 18 14 38 26 36 27 23 13 21 4 34 41 22 50 47 11 12 11 24 1 47 37 28 48 28 17 396 4 10 48 42 8 2 50 49 32 36 21 20 23 45 5 30 46 19 44 3 20 33

输出:

15

样例解释:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值