题目描述见Luogu P2462。
算法分析
其实这道题并不难,关键是如何转化。因为需要找到最长的单词接龙,就可以用图论来看。单词接龙不会出现环,所以,这就是个DAG
上的拓扑排序。如果两个单词可以接在一起,就必须满足以下条件:
- 前一个单词的字母都必须在后一个单词中出现过
- 任意一个字母都不能少
- 后一个单词的长度比前一个单词多
1
,不能多也不能少
因为没有对顺序作要求,我们只需记录其出现次数即可,并存储它们的哈希值(hash
/散列),枚举每个字符串的每个字母,增加其出现次数,并判断该字符串是否存在,如果存在,就建一条有向边。
最后,拓扑排序,记录答案并通过前驱指针递归输出。
代码实现
#include <bits/stdc++.h>
using namespace std;
#if __cplusplus < 201103 || !defined(__cplusplus)
typedef map<int,int> maptype;
#else
typedef unordered_map<int,int> maptype; // 如果是C++11及以上,使用无序哈希映射
#endif
struct edge
{
int to,nxt;
};
edge e[1000001]; int head[10001],tot;
int in[10001];
maptype mapping;
char str[10001][105];
int len[10001];
int cnt[10001][26];
int f[10001];
int pre[10001];
int n;
void connect(int x,int y){
e[++tot]=(edge){y,head[x]}; head[x]=tot; ++in[y];
}
int gethash(int idx){ // 哈希函数
int val=0;
for(register int i=0;i<26;++i){
val=val*23+cnt[idx][i];
}
return val;
}
void output(int d){ // 递归输出
if(pre[d]!=0){
output(pre[d]);
}
printf("%s\n",str[d]+1);
}
void topology(){ //拓扑排序
queue<int> q;
for(register int i=1;i<=n;++i){
if(!in[i]) q.push(i);
f[i]=1;
}
while(!q.empty()){
int x=q.front(); q.pop();
for(register int i=head[x],y;y=e[i].to,i;i=e[i].nxt){
if(f[y]<f[x]+1){
f[y]=f[x]+1;
pre[y]=x;
}
if(--in[y]==0){
q.push(y);
}
}
}
int ans=0;
for(register int i=1;i<=n;++i){
if(f[ans]<f[i]){
ans=i;
}
}
printf("%d\n",f[ans]);
output(ans);
}
void build(){ //建边
for(register int i=1;i<=n;++i){
for(register int j=0;j<26;++j){
++cnt[i][j];
int h=gethash(i);
if(mapping.find(h)!=mapping.end()){ // 如果存在一个可接的单词就建边
connect(i,mapping[h]);
}
--cnt[i][j]; // 要记得还原
}
}
}
void input(){
while(scanf("%s",str[++n]+1)!=EOF); --n; // 注意输入
for(register int i=1;i<=n;++i){
len[i]=strlen(str[i]+1);
for(register int j=1;j<=len[i];++j){
++cnt[i][str[i][j]-'a'];
}
mapping[gethash(i)]=i;
}
}
int main(){
input();
build();
topology();
return 0;
}
原文地址:https://www.cnblogs.com/ctjcalc/p/896656d.html
时间: 2024-10-05 08:00:23