[Luogu2462] [SDOI2007] 游戏 [字符串&hash&乱搞]

[ L i n k \frak{Link} Link]


先构造一个 trie


然后我有一个很暴力的想法
首先把字符串排序
然后按照顺序对每个字符串,我们把它首尾相接枚举起点
然后直接在 trie 里面暴力找?找得到就继承(类似 dp 转移那样)……
但是这样并不可行,复杂度太高了,承受不住


发现上面的过程可以看作是在图中转移
那么我们考虑对每个字符串,给它连边?
(枚举加上一个字符之后能够构成的所有字符串?)
这样的复杂度是 26 ∗ 2 L e n + 1 n 26*2^{Len+1}n 262Len+1n 那好像更沙雕了


我们考虑设计一种与字符所在位置无关的哈希
那么每次只需要随便多加一个就可以
26 n 26n 26n 预处理
然后我们在建出的 DAG 图里面随便搞一下
(因为这个 dag 的性质还有点特殊,所以你甚至可以直接 bfs )
(如果没有这个性质的话你可以拓扑排序也可以直接最长路)
(至于这个图边会有多少条。。。我就建议你开 26 n 26n 26n
实际上边数上限可能是把 n n n 分成多个 26 26 26 和一个余数 然后相邻层连边?
反正我建议 26 n 26n 26n

并且可以注意到所谓与字符所在位置无关
实际上就是说,我们只关心每种字母出现了几次
那实际上就相当于把字符串里面每种字母出现了几次记一下 然后做普通的哈希

我觉得最好搞双哈希或者加一个冲突处理啥的


那我们发现这道题里面的字符串我们只关心每种字母的出现次数
然后对于每个字符串我们就可以:
往 trie 里面塞一条长度为 26 的链,从上到下代表 abcdefg…
上面每个点代表的就是这个字母的出现次数
同样可以搞掉这道题。

虽然空间好像有点卡,写起来也有一点点沙雕


#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<stack>
#include<ctime>
#include<cstring>
#include<queue>
#include<cctype>
using namespace std;
int n = 1, tot;
string str[10005];
stack<int> stk;
int Siz[10005];
int Ap[10005][26];
int Markit[11451444];
const int MOD = 11451419;
int hzw[10005];
int head[10005], nxt[260005], to[260005];
int in[10005];
int Len[10005];
int Gkd[10005];
int Ans;
int AnsPos;
queue<int>Que;
#define add_edge(a,b) nxt[++tot] = head[a], head[a] = tot, to[tot] = b
int GetHash(const int& x)
{
	int Ret = 0;
    for (register int i = 0; i < 26; ++i)
    {
        Ret = (131ll*Ret) % MOD + Ap[x][i];
        Ret %= MOD;
    }
    return Ret;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    while(cin >> str[n])
    {
        Siz[n] = str[n].size();
        for (register int j = 0; j < Siz[n]; ++j)
        {
            ++Ap[n][str[n][j]-'a'];
        }
        Markit[hzw[n] = GetHash(n)] = n;
        ++n;
    }
    --n;
    for (register int itst, i = 1; i <= n; ++i)
    {
        for (register int j = 0; j < 26; ++j)
        {
            ++Ap[i][j];
            itst = GetHash(i);
            if (Markit[itst])
            {
                add_edge(i, Markit[itst]);
                ++in[Markit[itst]];
            }
            --Ap[i][j];
        }
    }
    for (register int i = 1; i <= n; ++i)
    {
        if (!in[i]) Que.push(i), Len[i] = 1;
    }
    while (!Que.empty())
    {
        int x = Que.front();
        Que.pop();
        for (register int i = head[x]; i; i = nxt[i])
        {
            --in[to[i]];
            Len[to[i]] = Len[x] + 1;
            Gkd[to[i]] = x;
            if (!in[to[i]]) Que.push(to[i]);
        }
    }
    for (register int i = 1; i <= n; ++i)
    {
    	if (Len[i] > Ans) Ans = Len[AnsPos = i];
    }
    cout<<Ans<<endl;
    while (AnsPos)
    {
        stk.push(AnsPos);
        AnsPos = Gkd[AnsPos];
    }
    while (!stk.empty())
    {
        cout << str[stk.top()] << endl;
        stk.pop();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值