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和一个出度 = 入度 + 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;
}