P1688 新单词接龙问题 题解

题目: P1688

大家好,我喜欢 Hash,所以我用 Hash 通过了这道题

思路: 哈希

处理方法有点类似于 P6521

首先我们最终接出的字符串要求其中的小字符串按字典序排序,那么我们就将所有字符串排好序后从前往后枚举(其实题目已经给我们排好序了,但有些人可能没有注意到),每次枚举到一个字符串就更新把它作为答案串结尾的最大长度。

然后注意到一一枚举从 az 的每一个字母会让复杂度爆掉,那么我们就用一个空字符(比如 #)来代替要变换的部分。

举个例子,对于字符串 adcb,它可以由 adb 加一个字符 c 变换过来,那么我们就会在枚举到 adb 时将状态 ad#b 塞到 Hash 中,之后枚举到 adcb 时就可以用 ad#b 来更新答案。

具体过程:

令当前枚举到第 \(i\) 个字符串,我们先更新这个字符串作为结尾的答案。
分三种变换(两种情况):

  1. 添加 & 修改一个字母
    枚举每一位字符,将其变为 # 后在 Hash 中查找该串,更新找到的最大长度。
  2. 删除一个字母(当前字符串长度不能为 \(16\)
    枚举每一个字符间的位置,把 # 插进去后在 Hash 中查找,更新找到的最大长度。

将找到的最大长度 \(+1\) 后更新到最终答案里。
然后再插入这个字符串,也分三种变换两种情况,但删除和添加操作调换了一下:

  1. 删除 & 修改一个字母
    枚举每一位字符,将其变为 # 后把它塞进 Hash。
  2. 添加一个字母(当前字符串长度不能为 \(16\)
    枚举每一个字符间的位置,把 # 插进去后把串塞进 Hash。

最后我们会发现这样会让相同的字符串重复统计,那么我们在一开始就把相同的字符串去掉就好,不影响最终结果。

更具体一点看代码(写的有点 Naive):

Naive Code:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline ll Max(ll x, ll y){return x > y ? x : y;}
inline ll rd(){
	ll x = 0;bool f = true;char c = getchar();
	while (c < '0' || c > '9'){if (c == '-') f = false; c = getchar();}
	while (c >= '0' && c <= '9'){x = (x<<3) + (x<<1) + (c ^ 48);c = getchar();}
    return f ? x : -x;
}
const int N = 2.5e4 + 10, M = 1e5 + 7;

class Hash_table{
    private: 

    struct Node{
        int nextt, num;// num 是 “str” 这个状态的最大长度
        string str;
    }data[N * 50];int head[M + 10], cnt;

    int h(string s){
        int l = s.size(), res = 0;
        for(int i = 0; i < l; ++i)
            res = ((res << 4) + int(s[i] - 'a') + M) % M;
        return res;
    }

    public: 

    int find(string s){
        int key = h(s);
        for(int i = head[key]; i; i = data[i].nextt)
            if(s == data[i].str)
                return data[i].num;
        return 0;
    }

    void insert(string s, int val){
        int key = h(s);
        for(int i = head[key]; i; i = data[i].nextt){
            if(s == data[i].str){
                data[i].num = Max(data[i].num, val);
                return ;
            }
        }
        data[++cnt].nextt = head[key];
        data[cnt].num = val;
        data[cnt].str = s;
        head[key] = cnt;
        return ;
    }
}H;

int n;
int ans;
string s, lst = "", tmp, tmp1, tmp2;// lst用来去重,一坨tmp用来枚举带’#’的字符串

int main(){
    n = rd();
    for(int i = 1; i <= n; ++i){
        cin >> s;
        if(s == lst)continue;
        lst = s;
        int l = s.size(), val = 0;// val是以当前串结尾的串的最大长度
		tmp = s;
        for(int j = 0; j < l; ++j){// 查找之前 添加和修改 的字符串
            tmp[j] = '#';
            val = Max(val, H.find(tmp));
			tmp[j] = s[j];
        }
        if(l < 16)// 查找之前 删除 的字符串
            for(int j = 0; j <= l; ++j){
                tmp = "", tmp1 = "", tmp2 = "";
                for(int k = 1; k <= j; ++k)// 用一堆 += 时间更优
                    tmp1 += s[k - 1];
                for(int k = j + 1; k <= l; ++k)
                    tmp2 += s[k - 1];
                tmp += tmp1;
                tmp += '#';
                tmp += tmp2;
                val = Max(val, H.find(tmp));
            }

        val++;
        ans = Max(ans, val);// 更新最终答案

		tmp = s;
        for(int j = 0; j < l; ++j){// 插入 删除和修改 后的字符串
            tmp[j] = '#';
            H.insert(tmp, val);
			tmp[j] = s[j];
        }

        if(l < 16)// 插入 添加 后的字符串
            ans = Max(ans, val);
            for(int j = 0; j <= l; ++j){
                tmp = "", tmp1 = "", tmp2 = "";
                for(int k = 1; k <= j; ++k)
                    tmp1 += s[k - 1];
                for(int k = j + 1; k <= l; ++k)
                    tmp2 += s[k - 1];
                tmp += tmp1;
                tmp += '#';
                tmp += tmp2;
                H.insert(tmp, val);
            }
    }
    printf("%d", ans);
	return 0;
}
posted @ 2022-11-09 14:52  Cotsheep  阅读(14)  评论(0编辑  收藏  举报