您的位置:首页 > 其它

NOIP 2015 提高组 Day1 信息传递

2018-01-11 08:16 477 查看
求最小的环的长度。 没思路大概这已经卡死一小半人。

我的起初的想复杂了。 后来觉得:这道题真简单: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 样例解释:
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: