传送门


如果\(r-l\)比较小,可以将所有满足条件的串扔进\(AC\)自动机然后在上面DP,从前往后确定字符串的每一位。

但是\(l,r \leq 10^{800}\)就十分不可行,所以需要优化这个算法。

考虑可能会有某一个节点的子节点连向的所有子节点构成一个满十叉树,意即当到达了这个节点之后可以随便往下走子节点,而且不论怎么走得到的结果都相同。比如说对于某一个九位数\(x = \overline {x_1x_2x_3x_4x_5x_6x_7x_8x_9}\),当前的\(l=123456789,r = 987654321\),那么若\(x_1 \in [2,8]\),无论\(x_2\)到\(x_9\)如何取值,\(x\)一定满足\(l \leq x \leq r\)。而当\(x_1 = 1,x2 \in [3,9]\)时都可以满足\(l \leq x \leq r\),无论\(x_3\)到\(x_9\)如何取值。

我们不妨把长度为\(k\)的“无论如何取值都能产生一个合法情况”的情况叫做产生一个\(k\)位通配符,比如说上面举的例子中\(x_1\in[2,8]\)时就会产生一个\(8\)位通配符,而\(x_1 = 1,x2 \in [3,9]\)则产生了一个\(7\)位通配符。特殊地,如果某个串刚好与\(l\)或\(r\)相等,我们认为产生了一个\(0\)位通配符(也就表示答案\(+1\))。

考虑如何会产生一个若干位的通配符。对于\(\geq l\)的情况,当\(\forall j \ x_j = l_j\)且\(x_{j+1} > l_{j+1}\),就会产生一个\(|l| - j - 1\)位的通配符,\(\leq r\)的情况类似,而如果\(|l| < |r|\),只要任意选择第一位为\(1\)到\(9\)的数,就可以产生\(|l| + 1\)到\(|r| - 1\)位的通配符。

可以发现最后产生的串的答案数量就是这个串中出现的通配符数量的总和。那么我们只需要计算出\(AC\)自动机上到达每一个节点能够产生的通配符数量,然后就可以比较快速地DP了。

将\(l,r\)两个串以及所有能够出现通配符情况的串放在一起构建Trie图/AC自动机,构建方法类似数位DP,对于每一个\(l\)和\(r\)的前缀枚举能够产生通配符的下一个字符来计算\(sum_{i,j}\)表示到达第\(i\)个节点能够获得的长度为\(j\)的通配符数量。注意通配符数量是可以通过\(fail\)指针传递的。具体细节请阅读下面代码中的insert函数和build函数

又设\(dp_{i,j}\)表示长度为\(i\)、当前所在节点为\(j\)的最大通配符数量,转移:\(dp_{i,j} + \sum\limits_{k=0}^{N-i-1}sum_{ch_{i , c} , k} \rightarrow dp_{i + 1 , ch_{i , c}}\)

后面的\(sum\)只需要枚举到\(N-i-1\)的原因是通配符的结尾不能超出字符串的结尾。

将\(sum\)前缀和,将复杂度做到\(O((|l| + |r|)NA^2)\),其中\(A\)为字符集大小。

输出方案倒推一边即可

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<stack>
#include<vector>
#include<cmath>
#include<random>
//This code is written by Itst
using namespace std; int N;
struct node{
int ch[10] , sum[2007] , fail;
}Trie[16017];
int cnt = 1 , L1 , L2;
char L[807] , R[807]; #define get(x , y) (!Trie[x].ch[y] ? Trie[x].ch[y] = ++cnt : Trie[x].ch[y]) void insert(){
L1 = strlen(L + 1) , L2 = strlen(R + 1);
int u = 1 , v = 1;
if(L1 == L2){
for(int j = 1 ; j <= L1 ; ++j)
if(u == v){
for(int k = L[j] - '0' + 1 ; k < R[j] - '0' ; ++k)
++Trie[get(u , k)].sum[L1 - j];
u = get(u , L[j] - '0');
v = get(v , R[j] - '0');
}
else{
for(int k = L[j] - '0' + 1 ; k <= 9 ; ++k)
++Trie[get(u , k)].sum[L1 - j];
u = get(u , L[j] - '0');
for(int k = j == 1 ; k < R[j] - '0' ; ++k)
++Trie[get(v , k)].sum[L2 - j];
v = get(v , R[j] - '0');
}
++Trie[u].sum[0];Trie[v].sum[0] += u != v;
}
else{
for(int j = 1 ; j <= L1 ; ++j){
for(int k = L[j] - '0' + 1 ; k <= 9 ; ++k)
++Trie[get(u , k)].sum[L1 - j];
u = get(u , L[j] - '0');
}
for(int j = 1 ; j <= L2 ; ++j){
for(int k = j == 1 ; k < R[j] - '0' ; ++k)
++Trie[get(v , k)].sum[L2 - j];
v = get(v , R[j] - '0');
}
for(int j = L1 + 1 ; j < L2 ; ++j)
for(int k = 1 ; k <= 9 ; ++k)
++Trie[get(1 , k)].sum[j - 1];
++Trie[u].sum[0];++Trie[v].sum[0];
}
} void build(){
queue < int > q;
for(int i = 0 ; i < 10 ; ++i)
if(!Trie[1].ch[i])
Trie[1].ch[i] = 1;
else{
Trie[Trie[1].ch[i]].fail = 1;
q.push(Trie[1].ch[i]);
}
while(!q.empty()){
int t = q.front();
q.pop();
for(int j = 0 ; j < L2 ; ++j)
Trie[t].sum[j] += Trie[Trie[t].fail].sum[j];
for(int i = 0 ; i < 10 ; ++i)
if(!Trie[t].ch[i])
Trie[t].ch[i] = Trie[Trie[t].fail].ch[i];
else{
Trie[Trie[t].ch[i]].fail = Trie[Trie[t].fail].ch[i];
q.push(Trie[t].ch[i]);
}
}
for(int i = 1 ; i <= cnt ; ++i)
for(int j = 1 ; j < N ; ++j)
Trie[i].sum[j] += Trie[i].sum[j - 1];
} void init(){
scanf("%s %s %d" , L + 1 , R + 1 , &N);
insert();
build();
} int dp[2007][16017];
bool can[2007][16017]; inline int maxx(int a , int b){
return a > b ? a : b;
} int main(){
init();
memset(dp , -0x3f , sizeof(dp));
dp[0][1] = 0;
for(int i = 0 ; i < N ; ++i)
for(int j = 1 ; j <= cnt ; ++j)
if(dp[i][j] >= 0)
for(int k = 0 ; k < 10 ; ++k)
dp[i + 1][Trie[j].ch[k]] = maxx(dp[i + 1][Trie[j].ch[k]] , dp[i][j] + Trie[Trie[j].ch[k]].sum[N - i - 1]);
int ans = 0;
for(int i = 1 ; i <= cnt ; ++i)
ans = maxx(ans , dp[N][i]);
cout << ans << endl;
for(int i = 1 ; i <= cnt ; ++i)
can[N][i] = (dp[N][i] == ans);
for(int i = N - 1 ; i >= 0 ; --i)
for(int j = 1 ; j <= cnt ; ++j)
if(dp[i][j] >= 0)
for(int k = 0 ; !can[i][j] && k < 10 ; ++k)
can[i][j] = can[i + 1][Trie[j].ch[k]] && (dp[i + 1][Trie[j].ch[k]] == dp[i][j] + Trie[Trie[j].ch[k]].sum[N - i - 1]);
int u = 1;
for(int i = 1 ; i <= N ; ++i)
for(int j = 0 ; j < 10 ; ++j)
if(can[i][Trie[u].ch[j]] && (dp[i][Trie[u].ch[j]] == dp[i - 1][u] + Trie[Trie[u].ch[j]].sum[N - i])){
putchar(j + '0');
u = get(u , j);
break;
}
return 0;
}

CF1110H Modest Substrings AC自动机、DP的更多相关文章

  1. POJ1625 Censored!(AC自动机+DP)

    题目问长度m不包含一些不文明单词的字符串有多少个. 依然是水水的AC自动机+DP..做完后发现居然和POJ2778是一道题,回过头来看都水水的... dp[i][j]表示长度i(在自动机转移i步)且后 ...

  2. HDU2296 Ring(AC自动机+DP)

    题目是给几个带有价值的单词.而一个字符串的价值是 各单词在它里面出现次数*单词价值 的和,问长度不超过n的最大价值的字符串是什么? 依然是入门的AC自动机+DP题..不一样的是这题要输出具体方案,加个 ...

  3. HDU2457 DNA repair(AC自动机+DP)

    题目一串DNA最少需要修改几个基因使其不包含一些致病DNA片段. 这道题应该是AC自动机+DP的入门题了,有POJ2778基础不难写出来. dp[i][j]表示原DNA前i位(在AC自动机上转移i步) ...

  4. hdu 4117 GRE Words AC自动机DP

    题目:给出n个串,问最多能够选出多少个串,使得前面串是后面串的子串(按照输入顺序) 分析: 其实这题是这题SPOJ 7758. Growing Strings AC自动机DP的进阶版本,主题思想差不多 ...

  5. hdu 2457(ac自动机+dp)

    题意:容易理解... 分析:这是一道比较简单的ac自动机+dp的题了,直接上代码. 代码实现: #include<stdio.h> #include<string.h> #in ...

  6. HDU 2425 DNA repair (AC自动机+DP)

    DNA repair Time Limit: 5000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total ...

  7. HDU2296——Ring(AC自动机+DP)

    题意:输入N代表字符串长度,输入M代表喜欢的词语的个数,接下来是M个词语,然后是M个词语每个的价值.求字符串的最大价值.每个单词的价值就是单价*出现次数.单词可以重叠.如果不止一个答案,选择字典序最小 ...

  8. tyvj P1519 博彩游戏(AC自动机+DP滚动数组)

    P1519 博彩游戏 背景 Bob最近迷上了一个博彩游戏…… 描述 这个游戏的规则是这样的:每花一块钱可以得到一个随机数R,花上N块钱就可以得到一个随机序列:有M个序列,如果某个序列是产生的随机序列的 ...

  9. bzoj 1030 [JSOI2007]文本生成器(AC自动机+DP)

    [题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1030 [题意] 给n个小串,随机构造一个长为m的大串,一个串合法当且仅当包含一个或多个 ...

随机推荐

  1. ionic打包报错Execution failed for task ':processDebugResources'

    ionic 打包的时候报了这样一个错误:Execution failed for task ':processDebugResources' 分析: compile "com.android ...

  2. JS列表

    promise 引用类型/值类型 ----- 对比python可变对象/不可变对象 原型继承

  3. 性能测试—JMeter 常用元件(四)

    <零成本web性能测试>第三章 Web性能测试脚本录制与开发中JMeter常用测试元件 测试计划描述了JMeter运行时将会执行的一系列步骤,一个完整的测试计划包含一个或多个线程组.逻辑控 ...

  4. MSSQL清理所有用户数据库日志(SQLSERVER2008)

    USE [master]; SET NOCOUNT ON; )=''; )=''; DECLARE @clearSql VARCHAR(MAX)=''; ; ,),TMP_WHILE_FLAG, T. ...

  5. mybatis学习--缓存(一级和二级缓存)

    声明:学习摘要! MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级) ...

  6. rpm安装时出现循环依赖

    在安装git包时提示要安装perl-git,当安装perl-git时又提示要安装git包.报错如下: [root@racdb1 Packages]# rpm -ivh perl-Git-1.7.1-4 ...

  7. oh-my-zsh安装与使用

    使用oh-my-zsh之前确保安装过zsh 通过脚本安装: sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussel ...

  8. MySQL使用索引的场景分析、不能使用索引的场景分析

    一.MySQL中能够使用索引的典型场景 1.匹配全值.对索引中的列都有等值匹配的条件.即使是在and中,and前后的列都有索引并进行等值匹配. 2.匹配值的范围查询,对索引的值能够进行范围查找. 3. ...

  9. 10LaTeX学习系列之---Latex的文档结构

    目录 目录 前言 (一)对于Ctex宏包中的文档结构 1.说明 2.源代码 3.输出效果 4.技巧 (二)对于ctexart的文档结构 1.说明 2.源代码 3.输出效果 (三)对于ctexbook的 ...

  10. scanf函数(初学者)

    scanf函数称为格式输入函数,即按用户指定的格式从键盘上把数据输入到指定的变量之中. 1.scanf函数的一般形式:scanf函数是一个标准的库函数,它的函数原型在头文件“stdio.h”中,与pr ...