传送门


如果\(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. python 类函数,实例函数,静态函数

    一.实现方法 class Function(object): # 在类定义中定义变量 cls_variable = "class varibale" def __init__(se ...

  2. FreeSSHD login with permission denied

    登录遇到问题: Permission denied, please try again. 解决方法: 在window中使用freesshd开启ssh后,客户端登陆时报 access denied错误 ...

  3. 使用过AsyncTask、EventBus、Volley以及Retrofit,必须好好了解handler运行机制

    我们都知道在UI线程中不能进行耗时操作,例如数据读写.网络请求.Android 4.0开始,在主线程中进行网络请求甚至会抛出Android.os.NetworkOnMainThreadExceptio ...

  4. JavaScript大杂烩15 - 使用JQuery(下)

    前面我们总结了使用各种selector拿到了jQuery对象了,下面就是对这个对象执行指定的行为了. 2. 操作对象 - 行为函数action 执行jQuery内置的行为函数的时候,JQuery自动遍 ...

  5. 使用Anemometer基于pt-query-digest将MySQL慢查询可视化

    最近玩MySQL,发现了一个很不错的工具,可以把MySQL慢查询可视化,方便我们去找出和分析慢询语句,搭建的步骤不多,但网上详细教程比较少,说得也不够详细,一不小心,估计得蛋痛一会,哈哈 Percon ...

  6. 用Python实现数据结构之优先级队列

    优先级队列 如果我们给每个元素都分配一个数字来标记其优先级,不妨设较小的数字具有较高的优先级,这样我们就可以在一个集合中访问优先级最高的元素并对其进行查找和删除操作了.这样,我们就引入了优先级队列 这 ...

  7. 编写一个BAT脚本协助运维人员遇到问题时候调测数据库是否有效连接成功的操作攻略

    简单摘要: 1.内网系统出现故障需要排查 2.运维人员不熟悉数据库操作,没法通过连接数据库和执行SQL语句的方式排查数据库及数据是否正常 3.解决方案:编写一个bat脚本,运维人员双击运行即可.   ...

  8. pip安装django的时候提示没有这个命令

    问题描述: 在安装pyenv安装完python的时候,用pip安装django提示没有这个命令 [root@zabbix ~]# pip install django== 2.0 pyenv: pip ...

  9. Head First Android --- Enable USB debugging on your device

    1. Enable USB debugging on your device    On your device, open “Developer options” (in Android 4.0 o ...

  10. 如何开启win10的上帝模式

    用了这么久的电脑,小编才知道还有“上帝模式”这一说,原谅小编的孤陋寡闻.翻阅资料才知道,上帝模式简单来说就是一个全能的控制面板,如控制面板的功能.界面个性化.辅助功能选项等方方面面的控制设置,几乎包含 ...