hdu1116 欧拉图的判定(点的度数的判定+用非递归的路径压缩的并查集/dfs对连通图的判定)

这篇博客讨论了如何将单词排列问题转化为欧拉图的判定,分析了无向图和有向图成为欧拉图的条件,并探讨了在解决此类问题时使用dfs和并查集的优缺点。通过实例展示了在限制内存的情况下,如何利用并查集高效地判断图的连通性,避免了递归带来的栈溢出和存储所有边可能导致的内存超限问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0)

题意:  给出一些单词,能否将所有单词(每个单词都要用上,给出几次就用几次)排成一列,且第i个单词的最后一个字母总是等于第i+1个单词的第一个字母。


分析:①这道题先转化成图的模型,每个单词的首字母和尾字母是图中的点,而单词是有向边。

            ②对于题目所要求的,寻找是否可以走完每条边且每条边只走一次,就变成了离散数学上的对于一个图是否是欧拉图(两种,是欧拉道路和欧拉回路)的判定。

            ③对于无向图有两个条件:要求该图是连通的,图中全为偶数度数的点或者有最多有两个奇点  (度数为奇数的点为奇点);

                对于有向图有两个条件:要求该图在忽略方向的情况下是连通的,图中全为偶数度数的点或者最多有两个奇点且其中一个点的入度比出度多1、另一个点的出度比入度多1。

            ④对于度数,建个数组每个点存一下出现的次数即可;对于判断图是否连通,可以用dfs或者并查集。


参考:

并差集及其优化(路径压缩和按秩合并)

趣讲并查集


自己做题时:  刚开始想仅仅用dfs并不用欧拉图相关知识,即每个单词的第一个字母和最后一个字母作为点,按照有向边进行深搜,直到所有边都被纳入,但是递归最高可能10^5次会超出栈的容量。如果用不递归的写法应该很麻烦,因为题意是要把所有单词都用一遍,而并不是单纯将所有点都遍历一遍,单纯用dfs找出是否有这样的路径并不容易。况且如下面第二份Memory Limit Exceeded代码中建立一个结构体数组,每个结构体里再建立一个字符数组,如果要满足题意则是(10^5)*(10^3)的个数,每个char两个字节(byte),就是2*(10^5)*(10^3) b,即2*(10^5)*(10^3) /1024 KB,约等于10^5KB的规模,而题目要求是小于32768KB,会超内存Memory Limit Exceeded,所以将所有边进行存储并不现实。每条边只存单词的首字母和尾字母或许可以一试.但不如用并查集更简单,只需要用两个长度大于26的数组存储26个字母即可,存储他们的入度和出度,然后10^5的边就不需要再存储了,一边输入一边再整理并查集,输入完毕以后看看所有的点的上级节点是不是同一个即可。如果单纯求图是否联通,用dfs也是可以的,与并查集都是O(n)时间复杂度,但是并查集应该稍微省一点内存(比如说,用dfs最先想到的是用二维的邻接矩阵存储(长、宽都是26)边,从任意一点出发进行深搜,每次遇到一个点就标记一下,结束之后扫描一下标记的数组,看看还有没有没标记的点。)

1)AC

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <algorithm>
#include <math.h>
#include <string>


using namespace std;
//const int maxn=100010;
const int maxn1=1010;
int flag=1;
int n;
int cnt=0;
//char zimu[26]={'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
int pre[30];
int rudu[30];
int chudu[30];
/*
struct Code{
    char letter[maxn1];
	char first_letter;
	char last_letter;
	int len;
	int bj;
}code[maxn];
*/
int Find(int a){//非递归,路径压缩
    int root=a;
    while(pre[root]!=root){
        root=pre[root];
    }
    int temp;
    while(pre[a]!=a){
        temp=pre[a];
        pre[a]=root;
        a=temp;
    }
    return a;
}
void Join(int a,int b){
    int p1=Find(a);
    int p2=Find(b);
    if(p1==p2){//不能替换为 if(pre[a]==pre[b]),Find()函数仍然是必要的,否则失去路径压缩的意义同时也得不到正确答案,比如Join(1,2) 1->2; Join(1,3) 1->2,2->3; Join(1,4) 1->2,2->4 [而此时,实际上应该1->2,2->3,3->4,因为不是Find()找到最高负责人,只是匆匆找到上一个负责人改变指向,从而出错] 
        return ;
    }
    pre[p1]=p2;//简单合并,非按秩合并
    return ;
}
int main()
{
	int kase;
	cin>>kase;
	//char g1='a';
	//int g2=g1-97;
	//cout<<g2<<endl;
	while(kase--){
        flag=1;
        cnt=0;
		scanf("%d",&n);
        memset(rudu,0,sizeof(rudu));
        memset(chudu,0,sizeof(chudu));


        ///并查集,判断是否是连通图
        for(int i=0;i<26;i++)
            pre[i]=i;
        char s[maxn1];
        int s_len;
		for(int i=0;i<n;i++){
            scanf("%s",s);
            s_len=strlen(s);
            int one=s[0]-'a';
            int sec=s[s_len-1]-'a';
            chudu[one]++;
			rudu[sec]++;
			
            Join(one,sec);


		}
		for(int i=0;i<26;i++){
            if(rudu[i]>0||chudu[i]>0){
                if(pre[i]==i){
                    cnt++;
                }
            }
		}
		if(cnt>1){
            cout<<"The door cannot be opened."<<endl;
            continue;
		}


        ///判断是否是欧拉图(包括欧拉道路和欧拉回路,后者图中没有奇数点,前者图中只有一对奇数点且)
		int sum=0;
		for(int i=0;i<26;i++){
            if(sum>2){
                flag=0;
                break;
            }
            if(rudu[i]!=chudu[i]){
                sum++;
                if(fabs(rudu[i]-chudu[i])!=1){
                    flag=0;
                    break;
                }
            }
		}
		if(flag==1){
			cout<<"Ordering is possible."<<endl;
		}
		else{
			cout<<"The door cannot be opened."<<endl;
		}
	}
	return 0;
}


2)Memory Limit Exceeded:

#include <string.h>
#include <stdio.h>
#include <algorithm>

using namespace std;
const int maxn=100010;
const int maxn1=1010;
int flag=0;
int n;
int sum=0;
//char zimu[26]={'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
struct Code{
	char letter[maxn1];
	char first_letter;
	char last_letter;
	int len;
	int bj;
}code[maxn];
struct Pos{
	int star;
	int endd;
}pos[27];//'a'==97,'a'-97==0
bool cmp(Code a,Code b){
	if(a.letter[0]<b.letter[0]){
		return 1;
	}
	return 0;
}
void Dfs(int id){
	if(flag==1){
		return ;
	}
	if(sum==n){
		flag=1;
		return ;
	}
	int cur_star=pos[code[id].last_letter-97].star;
	int cur_end=pos[code[id].last_letter-97].endd;
	if(cur_star==0){
		return ;
	}
	for(int i=cur_star ;i<=cur_end;i++){
		if(code[i].bj==0){
			code[i].bj=1;
			sum++;
			Dfs(i);
		}
	}
	return ;
}
int main()
{
	int kase;
	cin>>kase;
	//char g1='a';
	//int g2=g1-97;
	//cout<<g2<<endl;
	while(kase--){
		flag=0;
		scanf("%d",&n);
		memset(pos,0,sizeof(pos));

		for(int i=0;i<n;i++){
			scanf("%s",code[i].letter);
			code[i].len=strlen(code[i].letter);
			code[i].first_letter=code[i].letter[0];
			code[i].last_letter=code[i].letter[code[i].len-1];
			code[i].bj=0;
		}

		sort(code,code+n,cmp);

		int fir=0;
		for(int i=1;i<n;i++){
			if(code[i].first_letter!=code[i-1].first_letter){
				pos[code[i-1].first_letter-97].endd=i-1;
				pos[code[i-1].first_letter-97].star=fir;
				fir=i;
			}
		}
		pos[code[n-1].first_letter-97].endd=n-1;
		pos[code[n-1].first_letter-97].star=fir;

		for(int i=0;i<n;i++){
			code[i].bj=1;
			sum=1;
			Dfs(i);
			code[i].bj=0;
			sum=0;
			if(flag){
				break;
			}
		}

		if(flag==1){
			cout<<"Ordering is possible."<<endl;
		}
		else{
			cout<<"The door cannot be opened."<<endl;
		}
	}
	return 0;
}

3)

Description

Some of the secret doors contain a very interesting word puzzle. The team of archaeologists has to solve it to open that doors. Because there is no other way to open the doors, the puzzle is very important for us. 

There is a large number of magnetic plates on every door. Every plate has one word written on it. The plates must be arranged into a sequence in such a way that every word begins with the same letter as the previous word ends. For example, the word ``acm'' can be followed by the word ``motorola''. Your task is to write a computer program that will read the list of words and determine whether it is possible to arrange all of the plates in a sequence (according to the given rule) and consequently to open the door. 
 

Input

The input consists of T test cases. The number of them (T) is given on the first line of the input file. Each test case begins with a line containing a single integer number Nthat indicates the number of plates (1 <= N <= 100000). Then exactly Nlines follow, each containing a single word. Each word contains at least two and at most 1000 lowercase characters, that means only letters 'a' through 'z' will appear in the word. The same word may appear several times in the list. 
 

Output

Your program has to determine whether it is possible to arrange all the plates in a sequence such that the first letter of each word is equal to the last letter of the previous word. All the plates from the list must be used, each exactly once. The words mentioned several times must be used that number of times. 
If there exists such an ordering of plates, your program should print the sentence "Ordering is possible.". Otherwise, output the sentence "The door cannot be opened.". 
 

Sample Input

    
3 2 acm ibm 3 acm malform mouse 2 ok ok
 

Sample Output

    
The door cannot be opened. Ordering is possible. The door cannot be opened.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值