P1127 词链

方法1:开始想的是先把所有字符串按照字典序升序排一下,然后从前向后以每一个单词为起点爆搜一下,第一个得到的答案就是字典序最小的答案,这个做法是对的,但是どうしても会被卡掉,因为复杂度比较高(由于剪枝不是很方便,所以直接废掉)。

#include<iostream>
#include<string>
#include<vector>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 1010;

vector<string> node;
vector<int> path;
int n;
int st[N];

int dfs(int u, int cnt){
    st[u] = 1;
    
    if(cnt == n) return 1;
    
    for(int i = 0; i < n; i ++){
        if(st[i] == 0 && node[i][0] == node[u].back()){
            st[i] = 1;
            path.push_back(i);
            if(dfs(i, cnt + 1)) return 1;
            path.pop_back();
            st[i] = 0;
        }
    }
    
    return 0;
}

int main(){
    cin >> n;
    
    for(int i = 0; i < n; i ++){
        string s;
        cin >> s;
        
        node.push_back(s);
    }
    
    sort(node.begin(), node.end());
    
    for(int i = 0; i < n; i ++){
        path.push_back(i);
        int k = dfs(i, 1);
        
        if(k){
            for(int i = 0; i < path.size(); i ++){ 
                cout << node[path[i]];
                if(i + 1 < path.size()) cout << '.';
            }
            return 0;
        }
        memset(st, 0, sizeof st);
        path.pop_back();
    }
    
    cout << "***" << endl;
    
    return 0;
}

方法2:通过给的所有单词,建图(这个图不用实际地建出来,只需要用结点建立并查集(判断底图连通性)并记下每一个结点的入度和出度(判断欧拉图)即可),并判断是否为欧拉图,若是欧拉图则存在一条词链,否则不存在。

建图方法:以每一个单词的首尾字母作为结点,单词作为边建图(此图最多26个结点),所以如果此图是欧拉图,那么必存在一条通路,将所有边访问且仅访问一次,那么就找到了一条词链,否则必定不存在词链。

判断过程是:

  1. 由于欧拉图必定连通,所以可以初步判断一下底图(指和有向图长得一摸一样的无向图)是否连通(可用并查集判断),若不连通必不行。
  2. 欧拉图要么所有结点都满足入度 = 出度, 要么只存在一个入度 = 出度 + 1和一个出度 = 入度 + 1的结点,其他结点满足入度 = 出度,所以据此再推翻一批不存在词链的单词。

判断过后,图必满足abs(入度 - 出度) = 1的结点数cnt = 0或2

cnt = 0时,随便找一个结点出发都能一笔画,根据题目要求找以字典序最小的字母为首字母的单词出发dfs即可。

cnt = 2时,找到以那个出度 - 入度 = 1的字母(唯一)为首字母的单词作为起始点,dfs一遍就一定可以找到一条词链。

以上两种情况的dfs,由于之前已经按照单词字典序排序,所以找到的一定是字典序最小的词链。

综上,其实是利用欧拉图的概念快速找到词链的起始字母,然后搜一遍即可,省去了其他不可能的情况。

#include<iostream>
#include<string>
#include<vector>
#include<cstring>
#include<algorithm>

using namespace std;

const int T = 1010, N = 26;

vector<string> str;
vector<int> path;
int n;
int st[T];
int p[N], belong[N]; // belong[t]判断t是否在图中
int in[N], out[N];

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

int dfs(int u, int cnt){
    if(cnt == n) return 1;
    
    for(int i = 0; i < n; i ++){
        if(str[i][0] - 'a' == u && st[i] == 0){
            path.push_back(i);
            st[i] = 1;
            if(dfs(str[i].back() - 'a', cnt + 1)) return 1;
            st[i] = 0;
            path.pop_back();
        }
    }
    
    return 0;
}

int main(){
    cin >> n;
    
    for(int i = 0; i < n; i ++){
        string s;
        cin >> s;
        
        str.push_back(s);
    }
    
    sort(str.begin(), str.end()); // 排序确保搜索字典序最小
    
    for(int i = 0; i < N; i ++) p[i] = i;
    
    for(int i = 0; i < n; i ++){
        int a = str[i][0] - 'a', b = str[i].back() - 'a';
        int pa = find(a), pb = find(b);
        if(pa != pb) p[pb] = pa;
        
        belong[a] = belong[b] = 1;
        in[b] ++, out[a] ++;
    }
    
    // 判断底图连通性
    int flag = 0;
    for(int i = 0; i < N; i ++)
        if(p[i] == i && belong[i]) flag ++;
    
    if(flag > 1){
        cout << "***";
        return 0;
    }
    
    // 判断欧拉图
    int cnt = 0, start = 0;
    
    for(int i = 0; i < N; i ++){
        int k = out[i] - in[i];
        
        if(abs(k) > 1){
            cout << "***";
            return 0;
        }
        
        if(abs(k) == 1){
            cnt ++;
            if(k == 1) start = i; // 记下起始字母
        }
    }
    
    
    if(cnt != 0 && cnt != 2){
        cout << "***";
        return 0;
    }
    
    if(cnt == 2) dfs(start, 0);
    if(cnt == 0)
        for(int i = 0; i < N; i ++)
            if(belong[i]){
                dfs(i, 0);
                break;
            }
    
    for(int i = 0; i < path.size(); i ++){
        cout << str[path[i]];
        if(i < path.size() - 1) cout << '.';
    }
    
    return 0;
}
posted @ 2021-01-23 15:47  yys_c  阅读(175)  评论(0编辑  收藏  举报