CF1110H Modest Substrings AC自动机、DP
如果\(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的更多相关文章
- POJ1625 Censored!(AC自动机+DP)
题目问长度m不包含一些不文明单词的字符串有多少个. 依然是水水的AC自动机+DP..做完后发现居然和POJ2778是一道题,回过头来看都水水的... dp[i][j]表示长度i(在自动机转移i步)且后 ...
- HDU2296 Ring(AC自动机+DP)
题目是给几个带有价值的单词.而一个字符串的价值是 各单词在它里面出现次数*单词价值 的和,问长度不超过n的最大价值的字符串是什么? 依然是入门的AC自动机+DP题..不一样的是这题要输出具体方案,加个 ...
- HDU2457 DNA repair(AC自动机+DP)
题目一串DNA最少需要修改几个基因使其不包含一些致病DNA片段. 这道题应该是AC自动机+DP的入门题了,有POJ2778基础不难写出来. dp[i][j]表示原DNA前i位(在AC自动机上转移i步) ...
- hdu 4117 GRE Words AC自动机DP
题目:给出n个串,问最多能够选出多少个串,使得前面串是后面串的子串(按照输入顺序) 分析: 其实这题是这题SPOJ 7758. Growing Strings AC自动机DP的进阶版本,主题思想差不多 ...
- hdu 2457(ac自动机+dp)
题意:容易理解... 分析:这是一道比较简单的ac自动机+dp的题了,直接上代码. 代码实现: #include<stdio.h> #include<string.h> #in ...
- HDU 2425 DNA repair (AC自动机+DP)
DNA repair Time Limit: 5000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total ...
- HDU2296——Ring(AC自动机+DP)
题意:输入N代表字符串长度,输入M代表喜欢的词语的个数,接下来是M个词语,然后是M个词语每个的价值.求字符串的最大价值.每个单词的价值就是单价*出现次数.单词可以重叠.如果不止一个答案,选择字典序最小 ...
- tyvj P1519 博彩游戏(AC自动机+DP滚动数组)
P1519 博彩游戏 背景 Bob最近迷上了一个博彩游戏…… 描述 这个游戏的规则是这样的:每花一块钱可以得到一个随机数R,花上N块钱就可以得到一个随机序列:有M个序列,如果某个序列是产生的随机序列的 ...
- bzoj 1030 [JSOI2007]文本生成器(AC自动机+DP)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1030 [题意] 给n个小串,随机构造一个长为m的大串,一个串合法当且仅当包含一个或多个 ...
随机推荐
- css清除默认样式
CSS 清除默认样式 通常有以下几句就够了: *{margin:0;padding:0} li{list-style:none} img{vertical-align:top;border:non ...
- Flutter 依赖的那些事儿
Flutter 里面有2种库一样的东西, Package -creating a pure Dart component. like a new Widget. 这种是纯Dart,相当于你自己写的组件 ...
- 聊聊setTimeout和setInterval线程
在聊setTimeout和setInterval这两个事件的前,先聊另外一个与之密切关联的知识点,那就是线程(thread).而线程有常常跟另外一个词语--“进程”一起出现.那么何为线程?何为线程呢? ...
- [Android GMS 认证] keystore/keymaster/Attestation的问题
首先确定写入key,操作如下: 检查 /persist/data/sfs 目录下是否有key文件存在 adb shell ls -la /persist/data/sfs 做过key prov ...
- PHP类多继承的替代方案Traits
概述 traits是PHP5.4新进入的特性,其目的就是解决PHP的类不能多继承的问题.Traits不是类!不能被实例化.可以理解为一组能被不同的类都能调用到的方法集合.只需要在类中使用关键词use引 ...
- [20170904]11Gr2 查询光标为什么不共享脚本.txt
[20170904]11Gr2 查询光标为什么不共享脚本.txt --//参考链接下面的注解脚本:https://carlos-sierra.net/2017/09/01/poors-man-scri ...
- 第五章 绘图基础(ALTWIND)
线上箭头表示画线的方向.WINDING模式和ALTERNATE模式都会填充三个封闭的L型区域,号码从1到3.两个更小的内部区域,号码为4和5,在ALTERNATE模式下不被填充.但是在WINDING模 ...
- RD340服务器安装windows2003系统
RD340服务器安装windows2003系统云修网
- SQL Server 链接服务器连接 SQLite数据库文件
SQL Server数据库允许通过数据库驱动程序连接各类数据库并进行操作.以下是在SQL Server 2012 R2中建立SQLite的链接服务器. 一.下载SQLite数据库的ODBC驱动程序: ...
- ECstore后台报表显示空白问题解决办法
执行如下sql语句: INSERT INTO `sdb_ectools_analysis` (`id`, `service`, `interval`, `modify`) VALUES (1, 'b2 ...