P1127 词链 题解

这道题需要把单词看成有向图的边,单词的首字母和尾字母当做点,这样就能建立有向图

另外,这题需要打印边的编号,而不是点的编号,所以dfs时候需要push边的编号

 

#include <bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int n;
vector<int> g[N]; //g[i]存的是字母 i 出发的边的编号 
int in[N], out[N];
string w[N]; 
bool cmp(int a, int b){
    return w[a]<w[b];
}


vector<int> path;
int h[N];//记录当前遍历到的边的编号 
void dfs(int x){
    while(h[x]<g[x].size()){
        int e=g[x][h[x]]; h[x]++;
        int v=w[e].back()-'a';//转为点的编号 
        dfs(v);
        path.push_back(e);//回溯时把边加入path 
    }
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>w[i];
    }
    for(int i=1;i<=n;i++){
        int a=w[i].front()-'a', b=w[i].back()-'a';
        g[a].push_back(i);
        out[a]++;
        in[b]++;
    }
    int start=-1, s=0, t=0;
    for(int i=0;i<26;i++){
        int d=out[i]-in[i];
        if(d==1) s++, start=i;
        else if(d==-1) t++;
        else if(d!=0) {
            cout<<"***"; return 0;
        }
    }
    if(!(s==1&&t==1 || s==0&&t==0)){
        cout<<"***"; return 0;
    }
    
    for(int i=0;i<26;i++)
        sort(g[i].begin(), g[i].end(), cmp);

    //如果是欧拉回路 ,需要找一个起点 
    if(start==-1){
        for(int i=0;i<26;i++)
        if(out[i]) {
            start=i; break;
        }
    }
    
    dfs(start);
    reverse(path.begin(), path.end());
    //需要判断是否联通 
    if(path.size()!=n) {
        cout<<"***"; return 0;
    }
    for(int i=0;i<path.size()-1;i++)
        cout<<w[path[i]]<<".";
    cout<<w[path.back()];
    
    return 0;
}

 

 另一种写法,存图时候把终点和单词下标一起存,比较函数需要自定义一下

#include <bits/stdc++.h>
using namespace std;

const int N = 1000;
int n;
typedef pair<int, int> pii; // 边的终点和单词下标
vector<string> words;
vector<pii> adj[26];      // 存储每条边对应边的终点和单词下标
int in_deg[26], out_deg[26];

vector<int> path;
int cur_idx[26];          // Hierholzer 中每个点的当前出边指针

// 深度优先 Hierholzer,按照 adj[u] 中的顺序(已预排序)递归
void dfs(int u) {
  while (cur_idx[u] < (int)adj[u].size()) {
    // 取出当前边的终点和单词下标
    int v = adj[u][cur_idx[u]].first; // 当前边的终点
    int eid = adj[u][cur_idx[u]].second; // 当前边的下标
    cur_idx[u]++;                       // 递归前更新当前出边指针
    dfs(v);
    path.push_back(eid);
  }
}

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  cin >> n;
  words.resize(n);
  for (int i = 0; i < n; i++) {
    cin >> words[i];
  }


  // 建图、统计度数
  for (int i = 0; i < n; i++) {
    int u = words[i].front() - 'a';
    int v = words[i].back()  - 'a';
    adj[u].push_back({v, i});// 记录边的终点和单词下标
    out_deg[u]++;
    in_deg[v]++;

  }


  // 检查 Euler 路径/回路 的度数条件
  int start = -1, cnt1 = 0, cnt_1 = 0;
  for (int i = 0; i < 26; i++) {
    int d = out_deg[i] - in_deg[i];
    if (d == 1) {
      cnt1++;
      start = i;
    } else if (d == -1) {
      cnt_1++;
    } else if (d != 0) {
      cout << "***\n";
      return 0;
    }
  }
  if (!((cnt1 == 1 && cnt_1 == 1) || (cnt1 == 0 && cnt_1 == 0))) {
    cout << "***\n";
    return 0;
  }
  // 如果是欧拉回路,从最小字母开始;如果是欧拉路径,从 out-in=1 的顶点开始
  if (start == -1) {
    for (int i = 0; i < 26; i++) {
      if (out_deg[i] > 0) {
        start = i;
        break;
      }
    }
  }

  // 对每个顶点的出边按单词字典序排序
  for (int i = 0; i < 26; i++) {
    sort(adj[i].begin(), adj[i].end(),
         [&](pii a, pii b) {
            // 比较单词下标对应的单词字典序
            return words[a.second] < words[b.second];
         });
  }

  // Hierholzer 算法找欧拉路,结果保存在 path 中(逆序)
  dfs(start);

  // 如果没有用完所有边,则无解
  if ((int)path.size() != n) {
    cout << "***\n";
    return 0;
  }

  // 输出:逆序取出,然后用 '.' 连接
  reverse(path.begin(), path.end());
  cout << words[path[0]];
  for (int i = 1; i < n; i++) {
    cout << '.' << words[path[i]];
  }
  cout << '\n';

  return 0;
}

 

 

posted @ 2025-04-27 20:45  katago  阅读(34)  评论(0)    收藏  举报