推了一晚上,写题的时候万分激动,终于过了 —— 功夫不负有心人
题目大意:
给定一个包含 n n n个单词的字典,从中选择若干个单词,按字典序进行单词接龙,使得接龙的长度最大。
新单词接龙的规则:
- 单词变换:单词 w i w_i wi 添加一个字母,删除一个字母,或修改一个字母可以得到单词 w i + 1 w_{i+1} wi+1。
- 字典序接龙: w 1 , w 2 , … , w n w_1,w_2,…,w_n w1,w2,…,wn, 满足字典序逐渐增大
题目思路:
读完题目觉得 n n n很小,所以就把暴力写了一遍,稳稳的 T T T掉了
暴力的思路:将 每个串删除、添加、修改 后所能产生的所有字符串构成集合,并判断集合内的字符串存不存在,若存在并且字典序比存在的字符串小,则连接一条边 ( i , j ) (i,j) (i,j), i i i代表当前字符串, j j j是集合内的字符串, w ( i , j ) w(i,j) w(i,j)代表 i i i可以通过一次操作变为 j j j,并且字典序小于 j j j
考虑如何去优化上述暴力?
假设 s s s通过操作生成的字符串集合是 t t t,那么这些字符串不可能是全部有用的,毕竟一共就存在 n n n个,单从 n n n个字符串来言,集合 t t t已经远远大于 n n n。所以暴力的方法计算了很多很多很多没用的东西
所以,我们可以直接利用字典树,对每一位的操作在访问字典树的过程中实现。字典树首先的功能就是可以排除一些根本不可能存在的可能:
例如,字符集 = {“aa”,“ab”},那么"aa" - > "ca"是完全没必要的
这就优化了许多了,然后我们可以在每一位枚举时,加上字典序一定大于的当前字符串这个条件的剪枝,字典序小于的字符串不去考虑。那么如何实现这个呢?这里比较细节了,推了一晚上:
- 修改操作:因为只能修改一位的值,那么只能是将某个字符串的某一位的值进行扩大。
- 删除操作:首先,确定不可能删除最后一个字符。其次,假设删除第 k k k位的字符使得字典序变大,那么必然有 s k < s k + 1 s_k < s_{k+1} sk<sk+1
- 添加操作:假设在第k位之前添加一个字母 c c c,使得字典序变大,那么添加的字母 c c c必然满足: c > s k c > s_k c>sk。如果要添加一个 c = = s k c == s_k c==sk字母,那么必然要满足 s k > s k + 1 s_k>s_{k+1} sk>sk+1。最后判一下在最后添加的情况即可
N o t e : Note: Note:添加和当前位置相等字母时,多写推一推,因为必然会有一个分界点使得 s k ! = s k + 1 s_k != s_{k+1} sk!=sk+1,而差距就是在这产生的
考虑完这三种操作一定保证字典序大于
之后,这个题就剪枝的不能在剪枝了(至少在我范围内极限了)
接下来只需要写下 d f s dfs dfs函数即可,关于函数的细节就不说了。主要当时写的时候,为了乱套注释写的挺详细了,看注释吧。
上上上上上上上界复杂度: O ( ∑ ∣ S ∣ 2 ) O(\sum|S|^2) O(∑∣S∣2),实际远远小于。
36 m s 通 过 36ms通过 36ms通过
Code:
/*** keep hungry and calm CoolGuang! ***/
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#include<stdio.h>
#include<queue>
#include<algorithm>
#include<string.h>
#include<iostream>
#define debug(x) cout<<#x<<":"<<x<<endl;
#define dl(x) printf("%lld\n",x);
#define di(x) printf("%d\n",x);
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
const ll INF= 1e17+7;
const ll maxn = 1e5+700;
const ll mod= 1e9+7;
const ll up = 1e13;
const double eps = 1e-9;
const double PI = acos(-1);
template<typename T>inline void read(T &a){
char c=getchar();T x=0,f=1;while(!isdigit(c)){
if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){
x=(x<<1)+(x<<3)+c-'0';c=getchar();}a=f*x;}
ll n,m,p;
int t[maxn][26];
int idx[maxn*20],vis[maxn*20];
int cnt = 0;
int len;
char s[maxn][20];
void Insert(char *p,int id){
int rt = 0;
for(int i=1;i<=len;i++){
int op = p[i] - 'a';
if(!t[rt][op]) t[rt][op] = ++cnt;
rt = t[rt][op];
vis[rt] = 1;
}
idx[rt] = id;
}
int res[maxn];
void dfs(int rt,int top,int f,int id){
///root,当前指针的位置,是否操作过
///边界条件
//debug(top);
if(top == len+1){
res[id] = max(res[id],res[idx[rt]]);
if(!f){
///还可以添加
for(int i=0;i<26;i++){
int aim = t[rt][i];
if(aim) res[id] = max(res[id],res[idx[aim]]);
}
}
return ;
}
if(f){
///操作过
if(t[rt][s[id][top]-'a']) dfs(t[rt][s[id][top]-'a'],top+1,f,id);
return;
}
else{
///没操作过
int opx = s[id][top] - 'a',opy = s[id][top+1] - 'a';
///替换
//debug(opx);
//debug(opy);
for(int i=opx+1;i<26;i++)
if(t[rt][i]) dfs(t[rt][i],top+1,1,id);
///删除
if(top<len && opy > opx && t[rt][opy]) dfs(t[rt][opy],top+2,1,id);
///添加
for(int i=opx+1;i<26;i++)
if(t[rt][i])
dfs(t[rt][i],top,1,id);
if(t[rt][opx] && top<len && opx > opy) dfs(t[rt][opx],top,1,id);
}
///不动
if(t[rt][s[id][top]-'a']) dfs(t[rt][s[id][top]-'a'],top+1,f,id);
}
int main(){
read(n);
for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
for(int i=n;i>=1;i--){
len = strlen(s[i]+1);
dfs(0,1,0,i);
res[i]++;
Insert(s[i],i);
}
int ans = 0;
for(int i=1;i<=n;i++){
ans = max(ans,res[i]);
//printf("%d\n",res[i]);
}
di(ans);
return 0;
}