寒假集训日志(二)——最小生成树,拓扑排序,欧拉回路,连通路

  今天学的内容挺多的。

(一)首先说最小生成树,两种算法:

  1.Kruskal算法( 将边排序,然后再选,关键在于检查是否连通,使用并查集)

  2.Prim算法(使用点集,有点类似与最短路的算法)

  第一题是并查集算法的使用:

A - The Suspects(并查集)
Time Limit:1000MS     Memory Limit:20000KB     64bit IO Format:%I64d & %I64u
Submit Status

Description

严重急性呼吸系统综合症( SARS), 一种原因不明的非典型性肺炎,从2003年3月中旬开始被认为是全球威胁。为了减少传播给别人的机会, 最好的策略是隔离可能的患者。
在Not-Spreading-Your-Sickness大学( NSYSU), 有许多学生团体。同一组的学生经常彼此相通,一个学生可以同时加入几个小组。为了防止非典的传播,NSYSU收集了所有学生团体的成员名单。他们的标准操作程序(SOP)如下:
一旦一组中有一个可能的患者, 组内的所有成员就都是可能的患者。
然而,他们发现当一个学生被确认为可能的患者后不容易识别所有可能的患者。你的工作是编写一个程序, 发现所有可能的患者。
 

Input

输入文件包含多组数据。
对于每组测试数据:
第一行为两个整数n和m, 其中n是学生的数量, m是团体的数量。0 < n <= 30000,0 <= m <= 500。
每个学生编号是一个0到n-1之间的整数,一开始只有0号学生被视为可能的患者。
紧随其后的是团体的成员列表,每组一行。
每一行有一个整数k,代表成员数量。之后,有k个整数代表这个群体的学生。一行中的所有整数由至少一个空格隔开。
n = m = 0表示输入结束,不需要处理。

Output

对于每组测试数据, 输出一行可能的患者。

Sample Input

100 4
2 1 2
5 10 13 11 12 14
2 0 1
2 99 2
200 2
1 5
5 1 2 3 4 5
1 0
0 0

Sample Output

4
1
1
我的代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
#include<cstring>
using namespace std;
const int MAXN = 30010;

int fa[MAXN];
int Find ( int x){
    if( fa[x] == x)     return x;
    else return fa[x] = Find( fa[x] );
}

void Union ( int u , int v){
    int k = Find ( u);
    int g = Find ( v);
    fa[g] = k ;
}

int main(){
    int n, m;
    while(cin>>n>>m && (n!=0 || m!=0)){
        for( int i = 0 ; i<n; ++i){
            fa[i] = i ;
        }
        for( int i = 0 ; i < m ; ++i){
            int len;
            cin>>len ;  int before ; int now;
            for( int j= 0 ; j<len ; ++j){
                if( j== 0 )     cin>>now ;
                else{
                    before = now  ;
                    cin>> now ;
                    Union ( before , now);
                }
            }
            //输入合并完成
    }
    int flag = Find(0) ;   int ans = 0 ;
        for( int i = 0 ; i < n; ++i){
            if(Find(i) == flag ) ans ++;
    }
    cout<<ans<<endl;
}
return 0 ;
}

此处要注意的是,最后的判断步骤每个都要查到祖宗(使用find) 不能简单的查老爸;

另外,find的复杂度很小,据学长说几乎可以视为o(1);

 

A - 带权并查集(关系的转换十分巧妙!!看题解做的)
详解:http://blog.csdn.net/libing923/article/details/8240995
Time Limit:1000MS     Memory Limit:65536KB     64bit IO Format:%I64d & %I64u
Submit Status

Description

动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这N个动物所构成的食物链关系进行描述:
第一种说法是"1 X Y",表示X和Y是同类。
第二种说法是"2 X Y",表示X吃Y。
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。

Input

第一行是两个整数N和K,以一个空格分隔。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。

Output

只有一个整数,表示假话的数目。

Sample Input

100 7
1 101 1 
2 1 2
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5

Sample Output

3
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
#include<cstring>

using namespace std;
typedef long long LL;
const int maxN = 50010;

int fa[maxN], re[maxN];

int Find(int x){
    if(x!=fa[x]){
        int t = Find(fa[x]);
        re[x] = (re[x] + re[fa[x]])%3;
        fa[x] = t;
    }
    return fa[x];
}

bool Union(int d, int x, int y){
    int fx = Find(x), fy = Find(y);
    if(fx == fy){
        if( (re[y]-re[x]+3)%3!=d )  return true;
        else return false;
    }
    fa[fy] = fx;
    re[fy] = (re[x] - re[y] + d + 3)%3;
    return false;
}

int main()
{
    int n,k;
    scanf("%d%d", &n, &k);
    int a, b, c, ans=0;
    for(int i=0 ;i<maxN;++i){
        re[i]=0;    fa[i] = i;
    }
    while(k--){
        scanf("%d%d%d", &a, &b, &c);
        if(b>n||c>n)    ans++;
        else if(a==2 && b==c)   ans++;
        else if(Union(a-1,b,c)) ans++;
    }
    printf("%d\n", ans);
    return 0;
}

 

B - Agri-Net
Time Limit:1000MS     Memory Limit:10000KB     64bit IO Format:%I64d & %I64u
Submit Status

Description

Farmer John has been elected mayor of his town! One of his campaign promises was to bring internet connectivity to all farms in the area. He needs your help, of course.
Farmer John ordered a high speed connection for his farm and is going to share his connectivity with the other farmers. To minimize cost, he wants to lay the minimum amount of optical fiber to connect his farm to all the other farms.
Given a list of how much fiber it takes to connect each pair of farms, you must find the minimum amount of fiber needed to connect them all together. Each farm must connect to some other farm such that a packet can flow from any one farm to any other farm.
The distance between any two farms will not exceed 100,000.

Input

The input includes several cases. For each case, the first line contains the number of farms, N (3 <= N <= 100). The following lines contain the N x N conectivity matrix, where each element shows the distance from on farm to another. Logically, they are N lines of N space-separated integers. Physically, they are limited in length to 80 characters, so some lines continue onto others. Of course, the diagonal will be 0, since the distance from farm i to itself is not interesting for this problem.

Output

For each case, output a single integer length that is the sum of the minimum length of fiber required to connect the entire set of farms.

Sample Input

4
0 4 9 21
4 0 8 17
9 8 0 16
21 17 16 0

Sample Output

28
我的代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>

using namespace std;

const int MAXN = 110;
const int INF = 10e7;
int cost[MAXN][MAXN];
bool vis[MAXN];
int lowest[MAXN];
int ans;
int main()
{
    int T;
    while( cin>>T ){
        for( int i = 1 ; i<=T ; ++i)
            for( int j = 1; j<=T ; ++j)
                cin>>cost[i][j] ;

        ans = 0;
        for( int i = 1 ; i<=T ; ++i){
            vis[i]= 0;
        }
       vis [1] = 1 ;
       for( int i = 1 ; i<= T ; ++i){
        lowest[i] = cost[1][i] ;
       }

        for( int i = 1 ; i<= T ; ++i){
            int k=-1 ; int minn = INF;
            for( int j = 1 ; j<= T ; ++j){
                if( !vis[j] && lowest[j] < minn){
                    minn = lowest[j] ;
                    k = j;
                }
            }
            if( k==-1 ) break;
            vis [k] = 1;
            ans += lowest[k];
            for( int j = 1; j<= T ; ++j){
                if( !vis[j] && cost[k][j] < lowest[j])
                    lowest[j] = cost[k][j];
            }

        }

        cout<< ans <<endl;
    }
    return 0;
}

最短路,模板题。

(二)拓扑排序

   思想还是挺简单的,首先得知道入度,出度这两个概念。

   总的思想就是将入度为零的放在前面(或将出度为零的放在后面),因为没有了约束,然后再删掉边(就是直接减少与之相邻的度),如果又出现了入度为零的就丢到后面,如此循环。

 

C - 逃生
Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u
Submit Status

Description

糟糕的事情发生啦,现在大家都忙着逃命。但是逃命的通道很窄,大家只能排成一行。

现在有n个人,从1标号到n。同时有一些奇怪的约束条件,每个都形如:a必须在b之前。
同时,社会是不平等的,这些人有的穷有的富。1号最富,2号第二富,以此类推。有钱人就贿赂负责人,所以他们有一些好处。

负责人现在可以安排大家排队的顺序,由于收了好处,所以他要让1号尽量靠前,如果此时还有多种情况,就再让2号尽量靠前,如果还有多种情况,就让3号尽量靠前,以此类推。

那么你就要安排大家的顺序。我们保证一定有解。
 

Input

第一行一个整数T(1 <= T <= 5),表示测试数据的个数。
然后对于每个测试数据,第一行有两个整数n(1 <= n <= 30000)和m(1 <= m <= 100000),分别表示人数和约束的个数。

然后m行,每行两个整数a和b,表示有一个约束a号必须在b号之前。a和b必然不同。
 

Output

对每个测试数据,输出一行排队的顺序,用空格隔开。
 

Sample Input

1 5 10 3 5 1 4 2 5 1 2 3 4 1 4 2 3 1 5 3 5 1 2
 

Sample Output

1 2 3 4 5
 
我的代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
#include<cstring>
using namespace std;

const int maxPoint = 30010;

vector <int> Edge[maxPoint];
int indgree[maxPoint];
int main(){
    int T;
    scanf("%d",&T);
    while( T-- ){
        int n, m;
        int a , b;
        scanf("%d %d",&n,&m);
        memset( indgree , 0 , sizeof(indgree));
        for( int i = 1 ; i<= n ; ++i){
            Edge[i].clear();
        }
        for( int i= 1 ; i<=m ;++i){
                scanf("%d %d",&a,&b);
                Edge[b].push_back(a);
                indgree[a] ++;
        }
       priority_queue <int> q;
        for( int i = 1 ; i<=n; ++i){
            if( indgree[i] == 0)    q.push(i);
        }
      vector <int> res ;
        while( !q.empty()){
            int t = q.top() ;
            res.push_back(t);
             q.pop();
            for( int i = 0 ; i< Edge[t].size() ; ++i){
                int x = Edge[t][i] ;
                indgree[x] --;
                if(indgree[x] == 0 ) {
                    q.push(x);
                }
            }
        }
        for(int i = res.size() -1 ; i>= 0 ; --i){
            if( i == 0) printf("%d", res[i] );
            else    printf("%d ", res[i]);
        }
        puts("");
    }
return 0;
}

另外,此题的输入次数过多,用cin会超时。

 

(三)欧拉回路(转自师兄WhyWhy:http://www.cnblogs.com/whywhy/p/5067085.html)

问题:

  求一个无向图的一条欧拉路?也就是是否可以一笔画完?

 

  先判断图是否存在欧拉路,不存在直接返回。根据欧拉的定理:如果一个无向图有三个或者三个以上的点的度数是奇数,就没有欧拉路。

  先找到degree为奇数的一个点(不存在的话就任意点)作为开始点,进行DFS,每次走过一条边之后就删除这条边。然后当某个点无路可走的时候,把这个点加入答案数组。

  最后答案数组反向就是答案。

 

  伪代码如下:

 1 vector <int> ans;
 2 
 3 void dfs(int u) {
 4     if(u没有路可以继续下去) ans.push_back(u);
 5     else {
 6         for(e 是 u 出发的边) {
 7             delete e;
 8             dfs(e.to);
 9         }
10     }
11 }
12 
13 void getEuler() {
14     int start;
15     for(start=1;start<=N;++start)
16         if(degree(start)%2==1)
17             break;
18     if(start==N+1) start=1;
19 
20     dfs(start);
21     ans.reverse();        // ans反向。
22 }

例图如下:

 

                          从1开始dfs,找到1-2的边,删除,dfs 点2      然后找到边2-3,删除,dfs 点3

  

    然后找到边1-3,删除,dfs 点1。      这时dfs 点1无路可走,把1加入ans。然后回溯到3,  然后找到边4-5,删除,dfs 点5。

                        找到边3-4,删除,dfs 点4。

  然后找到边5-3,删除,dfs 点3        然后3无路可走,加入ans,回溯到5,然后5无路可走,加入ans,回溯到4,然后4无路可走,加入ans,回溯到3,然后3无

                       路可走,加入ans,回溯到2,然后2无路可走,加入ans,回溯到1,然后1无路可走,加入ans。

 

  这时ans数组是 1,3,5,4,3,2,1。

  倒序的话就是一条欧拉路。

  至于正确性的证明,严密的证明不会,但是如果一个点边很少的话一定要尽量往后被访问,不然进去就出不来了。所以这算是半个贪心策略,嗯。

懂是懂了。。。然而不会做题

D - Tanya and Password
Time Limit:2000MS     Memory Limit:262144KB     64bit IO Format:%I64d & %I64u
Submit Status

Description

While dad was at work, a little girl Tanya decided to play with dad's password to his secret database. Dad's password is a string consisting of n + 2 characters. She has written all the possible n three-letter continuous substrings of the password on pieces of paper, one for each piece of paper, and threw the password out. Each three-letter substring was written the number of times it occurred in the password. Thus, Tanya ended up with n pieces of paper.

Then Tanya realized that dad will be upset to learn about her game and decided to restore the password or at least any string corresponding to the final set of three-letter strings. You have to help her in this difficult task. We know that dad's password consisted of lowercase and uppercase letters of the Latin alphabet and digits. Uppercase and lowercase letters of the Latin alphabet are considered distinct.

Input

The first line contains integer n (1 ≤ n ≤ 2·105), the number of three-letter substrings Tanya got.

Next n lines contain three letters each, forming the substring of dad's password. Each character in the input is a lowercase or uppercase Latin letter or a digit.

Output

If Tanya made a mistake somewhere during the game and the strings that correspond to the given set of substrings don't exist, print "NO".

If it is possible to restore the string that corresponds to given set of substrings, print "YES", and then print any suitable password option.

Sample Input

Input
5
aca
aba
aba
cab
bac
Output
YES
abacaba
Input
4
abc
bCb
cb1
b13
Output
NO
Input
7
aaa
aaa
aaa
aaa
aaa
aaa
aaa
Output
YES
aaaaaaaaa
//第二天做出来了,year~~~,要注意一下此题如何复原字符串的
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<vector>
typedef long long LL;
using namespace std;
const int MAXN = 200010;

vector <int> edge[MAXN];
int in[MAXN];
int out[MAXN];
int vis[MAXN];
string ans;

void dfs ( int x){
    while( vis[x] < edge[x].size()){
        dfs( edge[x][ vis[x]++ ] );
    }
    ans +=(char)(x%200);
}
int main()
{
    int n;
    cin>>n;
    char str[10];

    memset( in , 0 , sizeof(in));
    memset( out, 0 , sizeof( out));
    memset( vis, 0 , sizeof( vis));

    int start ;
    for ( int i = 0 ; i< n; ++i){
        scanf("%s", str);
        int u = str[0] *200 + str[1]  ;
        int v = str[1] *200 + str[2] ;
        edge[u].push_back(v);
        out[u]++;   in[v]++;
        start = u;
    }

    int flag1 = 0 , flag2= 0;
    for( int i = 0 ; i < 100000 ; ++i){
        int d = in[i] - out[i] ;
        if( d== 1)  flag1++;
        else if( d == -1 ){
         flag2++;
         start = i;
         }
        else if( d!=0 ){
            cout<<"NO"<<endl;
            return 0;
        }
    }
    if( flag1 > 1 || flag2 > 1) {
        cout<<"NO"<<endl; return 0;
    }
    dfs( start);
    ans +=  start / 200 ;
    reverse( ans.begin() , ans.end());
    if( ans.length() == n +2) {
        cout<<"YES"<<endl;
        cout<<ans<<endl;
    }
    else{
        cout<<"NO"<<endl;
    }
    return 0;
}

 


(四)连通路
  1.Kosaraju 算法(两次DFS,正反一次,更为简单)
  2.Tarjan 算法(直接没听懂)
E - 迷宫城堡
Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u
Submit Status

Description

为了训练小希的方向感,Gardon建立了一座大城堡,里面有N个房间(N<=10000)和M条通道(M<=100000),每个通道都是 单向的,就是说若称某通道连通了A房间和B房间,只说明可以通过这个通道由A房间到达B房间,但并不说明通过它可以由B房间到达A房间。Gardon需要 请你写个程序确认一下是否任意两个房间都是相互连通的,即:对于任意的i和j,至少存在一条路径可以从房间i到房间j,也存在一条路径可以从房间j到房间 i。
 

Input

输入包含多组数据,输入的第一行有两个数:N和M,接下来的M行每行有两个数a和b,表示了一条通道可以从A房间来到B房间。文件最后以两个0结束。
 

Output

对于输入的每组数据,如果任意两个房间都是相互连接的,输出"Yes",否则输出"No"。
 

Sample Input

3 3 1 2 2 3 3 1 3 3 1 2 2 3 3 2 0 0
 

Sample Output

Yes No
我的代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
#include<cstring>
using namespace std;
const int MAXN = 10010;

vector <int> edge1[MAXN];
vector <int> edge2[MAXN];
vector <int> s;
int mark[MAXN];
void dfs1( int x ){
    mark[x] = 1;
    for( int i = 0; i< edge1[x].size() ; ++i){
        if( !mark[ edge1[x][i] ] ){
            dfs1( edge1[x][i] );
        }
    }
    s.push_back(x);
}

void dfs2( int x){
    mark[x] = 1;
    for( int i =0 ; i< edge2[x].size() ; ++i){
        if( !mark[ edge2[x][i] ] ){
            dfs2( edge2[x][i] );
        }
    }
}

int main(){
    int n, m;
    while( cin>>n>>m && ( n!=0 || m!= 0 )){
        int a, b;
        s.clear();
        s.push_back(0);
        memset( mark , 0 , sizeof( mark) );
        for( int i = 0 ; i<=n; ++i){
            edge1[i].clear();
            edge2[i].clear();
        }

        for( int i = 0 ; i< m ; ++i){
            cin>>a>>b;
            edge1[a].push_back(b);
            edge2[b].push_back(a);
        }



        for( int i =1 ; i<= n; ++i){
            if( !mark[i] )
                dfs1(i);
        }

        int ans = 0 ;
        memset( mark , 0 , sizeof( mark) );

        for( int i = n ; i>=1 ; --i){
            if( !mark[ s[i] ] ){
                ans++;
                dfs2( s[i] );
            }
        }
        if( ans == 1) cout<<"Yes"<<endl;
        else cout<<"No"<<endl;
    }
return 0;
}

判断是否都连通就是判断他的强连通分量是否为1呗。

今天任务没有完成,第D题没有攻克下来,明天加油。

posted @ 2016-01-24 09:09  W2W  阅读(386)  评论(0编辑  收藏  举报