【LDUOJ】2019老生专项训练2 J - 新单词接龙 | 字典树、剪枝

推了一晚上,写题的时候万分激动,终于过了 —— 功夫不负有心人

题目大意:

给定一个包含 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(S2),实际远远小于。

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;
}

猜你喜欢

转载自blog.csdn.net/qq_43857314/article/details/112598448