bzoj4502 串

题目背景:

bzoj4502

分析:

20% 解法:

暴力枚举前缀,然后两两枚举形成新串,然后有hash判重(用set的宝宝,爆成了5分·····)

50% 解法:

将集合中的所有的字符串构建成一个AC自动机,然后,注意到每个字符串在AC自动机上的匹配路径应该是唯一的,那么我们可以考虑dpf[i][j][k]表示当前长度为i的字符串,匹配到j状态,第一次的失配的位置在k处,然后直接暴力枚举下一个字符的位置,然后在AC自动机上跑转移就可以了,注意每一次的时的状态的深度一定要大于当前的第一次失配后的匹配长度,否则意味着无法满足题目的信息

100% 解法:

我们可以发现,在上一道题中的在第一次失配前的长度是不需要记录的,那么我们就可以考虑优化状态,f[i][j]表示,当前在第一次失配后的长度,然后当前匹配到的状态,同样注意,每一次失配的状态的深度一定要大于当前的i否则不满足形成前缀。

注意:因为我们一开始就是枚举的在第一次失配后走了多长的长度,但是可能我们可以选择直接在第一次失配后不再往后走,也就是直接将原来的某个字符串的一部分,直接改成一段前缀,想要判断这一个状态是否满足,只需要直接判断当前的fail指针是否为根,如果不是,则就意味着,是可以通过将当前的一段后缀替换为另一段前缀来实现。

Source

#include 
   
   
    
    
#include 
    
    
     
     
#include 
     
     
      
      
#include 
      
      
       
       
#include 
       
       
         #include 
        
          #include 
         
           #include 
          
            using namespace std; const int MAXL = 30 + 5; const int MAXN = 10000 + 10; struct node { node *next[26], *fail; bool exist[26]; int deep; node() { memset(next, 0, sizeof(next)), fail = NULL; memset(exist, 0, sizeof(exist)), deep = 0; } } trie[MAXL * MAXN], *root, *tot = trie; long long dp[MAXL][MAXL * MAXN]; inline void insert(char *s) { int len = strlen(s); node *cur = root; for (int i = 0; i < len; ++i) { if (!cur->next[s[i] - 'a']) cur->next[s[i] - 'a'] = tot++; cur->exist[s[i] - 'a'] = true, cur = cur->next[s[i] - 'a']; } } inline void build_fail() { queue 
           
             q; q.push(root), root->deep = 0; while (!q.empty()) { node *cur = q.front(); q.pop(); for (int i = 0; i < 26; ++i) { if (cur->next[i]) { node *v = cur->fail; while (v && !v->next[i]) v = v->fail; cur->next[i]->fail = (v ? v->next[i] : root); q.push(cur->next[i]), cur->next[i]->deep = cur->deep + 1; } else { node *v = cur->fail; while (v && !v->next[i]) v = v->fail; cur->next[i] = (v ? v->next[i] : root); } } } } inline void dp_on_AC_automation() { long long ans = 0; /*判断可以直接通过改变原串的一部分*/ for (node *i = trie; i != tot; ++i) if (i->fail != root) ans++; /*计算需要两串进行拼接的部分的开头位置*/ for (node *i = trie; i != tot; ++i) for (int j = 0; j < 26; ++j) if (!i->exist[j] && i->next[j] != root) dp[1][i->next[j] - trie]++; /*枚举走到的状态和当前的步数*/ for (int i = 1; i < MAXL; ++i) for (node *j = trie; j != tot; ++j) { if (dp[i][j - trie]) { ans += dp[i][j - trie]; for (int k = 0; k < 26; ++k) if (j->exist[k] || j->next[k]->deep > i) dp[i + 1][j->next[k] - trie] += dp[i][j - trie]; } } cout << ans; } int n; char s[MAXL]; int main() { root = new node(); cin >> n; for (int i = 1; i <= n; ++i) cin >> s, insert(s); build_fail(); dp_on_AC_automation(); return 0; } 
            
           
          
         
       
      
      
     
     
    
    
   
   

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值