欧拉路径/并查集 - Play on Words - POJ 1386

欧拉路径/并查集 - Play on Words - POJ 1386

题意:

有 N 个盘子,每个盘子上写着一个仅由小写字母组成的英文单词。

你需要给这些盘子安排一个合适的顺序,使得相邻两个盘子中,前一个盘子上单词的末字母等于后一个盘子上单词的首字母。

请你编写一个程序,判断是否能达到这一要求。

输入格式

第一行包含整数 T,表示共有 T 组测试数据。

每组数据第一行包含整数 N,表示盘子数量。

接下来 N 行,每行包含一个小写字母字符串,表示一个盘子上的单词。

一个单词可能出现多次。

输出格式

如果存在合法解,则输出”Ordering is possible.”,否则输出”The door cannot be opened.”。

数据范围

1 ≤ N ≤ 1 0 5 , 单 词 长 度 均 不 超 过 1000 1≤N≤10^5, 单词长度均不超过1000 1N105,1000

输入样例:

3
2
acm
ibm
3
acm
malform
mouse
2
ok
ok

输出样例:

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

分析:

建图:

建 图 方 式 是 重 中 之 重 , 由 于 单 词 的 数 量 在 1 0 5 数 量 级 , 建图方式是重中之重,由于单词的数量在10^5数量级, 105

考 虑 到 单 词 连 接 的 方 式 是 首 尾 相 连 , 故 我 们 可 以 将 每 个 单 词 看 作 一 条 边 , 边 的 端 点 为 首 部 字 母 和 末 尾 字 母 。 考虑到单词连接的方式是首尾相连,故我们可以将每个单词看作一条边,边的端点为首部字母和末尾字母。

问题就转化为给定m条有向边,判断是否存在欧拉路径。

有向图的欧拉路径:

① 、 所 有 点 的 入 度 与 出 度 均 相 等 。 或 者 , ①、所有点的入度与出度均相等。或者,

② 、 起 点 的 出 度 比 入 度 大 1 , 终 点 的 入 度 比 出 度 大 1 , 其 他 点 的 入 度 与 出 度 相 等 。 ②、起点的出度比入度大1,终点的入度比出度大1,其他点的入度与出度相等。 11

本题还需注意连通性问题:

判 断 是 否 存 在 孤 立 点 : 判断是否存在孤立点:

① 、 并 查 集 : 若 存 在 两 个 点 的 祖 宗 节 点 不 同 , 说 明 存 在 孤 立 点 。 ①、并查集:若存在两个点的祖宗节点不同,说明存在孤立点。

② 、 欧 拉 路 径 : d f s 跑 一 遍 欧 拉 路 径 , 若 经 过 的 边 的 数 量 小 于 m , 说 明 存 在 孤 立 点 。 ②、欧拉路径:dfs跑一遍欧拉路径,若经过的边的数量小于m,说明存在孤立点。 dfsm

方法一:欧拉路径:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>

using namespace std;

const int N=30, M=100010;

int T,n=26,m,cnt;
int din[N],dout[N];
int g[N][N];
char str[1010];

bool has_euler()    //判断充要条件
{
    int start=0,end=0;
    for(int i=0;i<n;i++)
        if(din[i]!=dout[i])
        {
            if(din[i]+1==dout[i]) start++;
            else if(dout[i]+1==din[i]) end++;
            else return false;
        }
        
    if(!(!start && !end || start==1 && end==1)) return false;  //同时确保了奇数度的点数量为偶数

    return true;
}

void dfs(int u)
{
    for(int i=0;i<n;i++)
        if(g[u][i])
        {
            cnt++;
            g[u][i]--;
            dfs(i);
        }
}

int main()
{
    scanf("%d",&T);
    while(T--)
    {
        cnt=0;
        memset(g,0,sizeof g);
        memset(din,0,sizeof din);
        memset(dout,0,sizeof dout);

        scanf("%d",&m);
        for(int i=0;i<m;i++)
        {
            scanf("%s",str);
            int len=strlen(str);
            int a=str[0]-'a', b=str[len-1]-'a';
            g[a][b]++;
            din[b]++, dout[a]++;
        }
        
        int S=0;
        while(!dout[S]) S++;
        for(int i=0;i<n;i++)
            if(din[i]+1==dout[i])
            {
                S=i;
                break;
            }

        if(has_euler()) dfs(S);
        
        if(cnt<m) puts("The door cannot be opened.");
        else puts("Ordering is possible.");
    }

    return 0;
}

方法二:并查集:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>

using namespace std;

const int N=30, M=100010;

int T,n=26,m;
int p[N];
int din[N],dout[N];
bool st[N];
char str[1010];

int Find(int x)
{
    if(p[x]!=x) p[x]=Find(p[x]);
    return p[x];
}

bool is_connected()    //连通性判断
{
    int root=-1;
    for(int i=0;i<n;i++)
        if(st[i])
        {
            if(root==-1) root=Find(i);
            if(root!=Find(i)) return false;
        }
    return true;
}

bool has_euler()    //判断充要条件
{
    int start=0,end=0;
    for(int i=0;i<n;i++)
        if(din[i]!=dout[i])
        {
            if(din[i]+1==dout[i]) start++;
            else if(dout[i]+1==din[i]) end++;
            else return false;
        }
        
    if(!(!start && !end || start==1 && end==1)) return false;  //同时确保了奇数度的点数量为偶数

    return true;
}

int main()
{
    scanf("%d",&T);
    while(T--)
    {
        for(int i=0;i<n;i++) p[i]=i;
        memset(st,false,sizeof st);
        memset(din,0,sizeof din);
        memset(dout,0,sizeof dout);
        
        scanf("%d",&m);
        for(int i=0;i<m;i++)
        {
            scanf("%s",str);
            int len=strlen(str);
            int a=str[0]-'a', b=str[len-1]-'a';
            din[b]++, dout[a]++;
            st[b]=st[a]=true;
            p[Find(a)]=Find(b);
        }
        
        if(is_connected() && has_euler()) puts("Ordering is possible.");
        else puts("The door cannot be opened.");
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值