这个题解法之妙导致不想吐槽这场比赛了。。。
原题:Codeforces 750E New Year and Old Subsequence
复现:2019南昌网络赛C题 Hello 2019
原题题意:给定一个数字串,多次询问,每次询问使 [ l , r ] [l,r] [l,r]区间内存在"2017"这个序列,而不存在"2016"这个序列,至少需要删除多少个元素(无解输出 − 1 -1 −1)
复现题意:给定一个数字串,多次询问,每次询问使 [ l , r ] [l,r] [l,r]区间内存在"9102"这个序列,而不存在"8102"这个序列,至少需要删除多少个元素(无解输出 − 1 -1 −1)
思路:参考cf题解
先讲原题
- 在计算理论中,确定有限状态自动机或确定有限自动机(英语:deterministic finite automaton, DFA)是一个能实现状态转移的自动机。对于一个给定的属于该自动机的状态和一个属于该自动机字母表 Σ {\displaystyle \Sigma } Σ的字符,它都能根据事先给定的转移函数转移到下一个状态(这个状态可以是先前那个状态)。
- 而本题我们考虑如下
5
5
5种状态:
状态 0 0 0:空状态 o r or or 初始状态
状态 1 1 1:包含序列“ 2 2 2”
状态 2 2 2:包含序列“ 20 20 20”
状态 3 3 3:包含序列“ 201 201 201”
状态 4 4 4:包含序列“ 2017 2017 2017” - 如果我们将从 i i i状态转移到 j j j状态的过程看作一条花费为 c c c的边,则我们可以得到“ 2 2 2”,“ 0 0 0”,“ 1 1 1”,“ 7 7 7”,“ 6 6 6” 这 5 5 5个字符所对应的自动机,并且把这样 5 × 5 5×5 5×5的状态转移边放到一个矩阵中,就可以简易地表示自动机。考虑如下代码:
if(s[l]=='2') t[now].v[0][1]=0, t[now].v[0][0]=1;
if(s[l]=='0') t[now].v[1][2]=0, t[now].v[1][1]=1;
if(s[l]=='1') t[now].v[2][3]=0, t[now].v[2][2]=1;
if(s[l]=='7') t[now].v[3][4]=0, t[now].v[3][3]=1;
if(s[l]=='6') t[now].v[3][3]=1, t[now].v[4][4]=1;
- 这 5 5 5个矩阵未被声明的元素都被定义为 i n f inf inf(主对角线为 0 0 0),表明当前自动机在接收这个字符时(这 5 5 5种字符),由 i i i状态转移到 j j j状态的最小花费为多少。拿 s [ i ] = 2 s[i]=2 s[i]=2来举例,若当前自动机需要接受字符’ 2 2 2’,则从 0 0 0状态(空状态)转移到 1 1 1状态(包含序列“ 2 2 2”),所需最小花费为 0 0 0;而从 0 0 0状态(空状态)转移到 0 0 0状态(空状态)的最小花费为 1 1 1,因为必须要删除当前所接受的字符才行;同时,其他除了主对角线上的转移都是不合法的,因此保留 i n f inf inf边,且主对角线上的所有转移(自身转移到自身)都是不需要有任何花费的,因为发生不了转移。后面 4 4 4种矩阵同理。但需要注意的是,在当前自动机接受字符’ 6 6 6‘时,从 3 3 3状态(包含序列“ 201 201 201”)到 3 3 3状态(包含序列“ 201 201 201”),必须删除这个’ 6 6 6'才行,因此花费为 1 1 1;从 4 4 4状态(包含序列“ 2017 2017 2017”)到 4 4 4状态(包含序列“2017”),也必须删除掉这个‘ 6 6 6’才行,因此花费也为 1 1 1,而其他的转移跟之前的一样啦。
- 而当我们想要合并两个自动机时,也就是合并两个串时,我们考虑像 f l o y d floyd floyd一样的思路,考虑由 i i i状态转移到 j j j状态中间经过 k k k状态(枚举 k k k状态)的最小花费
- 而如果用线段树维护自动机的合并的话,那是不是就非常好写并且清晰呢?废话不多说,这代码绝对好懂!
Codeforces 750E
#include "bits/stdc++.h"
#define hhh printf("hhh\n")
#define see(x) (cerr<<(#x)<<'='<<(x)<<endl)
using namespace std;
typedef long long ll;
typedef pair<int,int> pr;
inline int read() {int x=0;char c=getchar();while(c<'0'||c>'9')c=getchar();while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();return x;}
const int maxn = 2e5+10;
const int M = 5;
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
const double eps = 1e-7;
struct Mat{
int v[M][M];
void O() { memset(v,inf,sizeof(v)); }
void I() { O(); for(int i=0; i<M; ++i) v[i][i]=0; }
friend Mat operator * (Mat a, Mat b) {
Mat c; c.O();
for(int i=0; i<5; ++i) for(int j=0; j<5; ++j) for(int k=0; k<5; ++k)
c.v[i][j]=min(c.v[i][j],a.v[i][k]+b.v[k][j]);
return c;
}
}t[maxn<<2];
int n, m, i, j;
char s[maxn];
void build(int l, int r, int now) {
if(l==r) {
t[now].I();
if(s[l]=='2') t[now].v[0][1]=0, t[now].v[0][0]=1;
if(s[l]=='0') t[now].v[1][2]=0, t[now].v[1][1]=1;
if(s[l]=='1') t[now].v[2][3]=0, t[now].v[2][2]=1;
if(s[l]=='7') t[now].v[3][4]=0, t[now].v[3][3]=1;
if(s[l]=='6') t[now].v[3][3]=1, t[now].v[4][4]=1;
return;
}
int m=(l+r)/2;
build(l,m,now<<1), build(m+1,r,now<<1|1), t[now]=t[now<<1]*t[now<<1|1];
}
Mat query(int x, int y, int l, int r, int now) {
if(x<=l&&r<=y) return t[now];
int m=(l+r)/2;
Mat res; res.I();
if(x<=m) res=query(x,y,l,m,now<<1);
if(y>m) res=res*query(x,y,m+1,r,now<<1|1);
return res;
}
int main() {
//ios::sync_with_stdio(false); cin.tie(0);
n=read(), m=read(); scanf("%s", s+1);
build(1,n,1);
while(m--) {
i=read(), j=read();
printf("%d\n", (i=query(i,j,1,n,1).v[0][4])==inf?-1:i);
}
}
2019南昌网络赛C
#include "bits/stdc++.h"
#define hhh printf("hhh\n")
#define see(x) (cerr<<(#x)<<'='<<(x)<<endl)
using namespace std;
typedef long long ll;
typedef pair<int,int> pr;
inline int read() {int x=0;char c=getchar();while(c<'0'||c>'9')c=getchar();while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();return x;}
const int maxn = 2e5+10;
const int M = 5;
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
const double eps = 1e-7;
struct Mat{
int v[M][M];
void O() { memset(v,inf,sizeof(v)); }
void I() { O(); for(int i=0; i<M; ++i) v[i][i]=0; }
friend Mat operator * (Mat a, Mat b) {
Mat c; c.O();
for(int i=0; i<5; ++i) for(int j=0; j<5; ++j) for(int k=0; k<5; ++k)
c.v[i][j]=min(c.v[i][j],b.v[i][k]+a.v[k][j]); //这里a到b的转移改成b到a的转移,就相当于把原串翻转了
return c;
}
}t[maxn<<2];
int n, m, i, j;
char s[maxn];
void build(int l, int r, int now) {
if(l==r) {
t[now].I();
if(s[l]=='2') t[now].v[0][1]=0, t[now].v[0][0]=1;
if(s[l]=='0') t[now].v[1][2]=0, t[now].v[1][1]=1;
if(s[l]=='1') t[now].v[2][3]=0, t[now].v[2][2]=1;
if(s[l]=='9') t[now].v[3][4]=0, t[now].v[3][3]=1; //这里数字小改一下
if(s[l]=='8') t[now].v[3][3]=1, t[now].v[4][4]=1;
return;
}
int m=(l+r)/2;
build(l,m,now<<1), build(m+1,r,now<<1|1), t[now]=t[now<<1]*t[now<<1|1];
}
Mat query(int x, int y, int l, int r, int now) {
if(x<=l&&r<=y) return t[now];
int m=(l+r)/2;
Mat res; res.I();
if(x<=m) res=query(x,y,l,m,now<<1);
if(y>m) res=res*query(x,y,m+1,r,now<<1|1);
return res;
}
int main() {
//ios::sync_with_stdio(false); cin.tie(0);
n=read(), m=read(); scanf("%s", s+1);
build(1,n,1);
while(m--) {
i=read(), j=read();
printf("%d\n", (i=query(i,j,1,n,1).v[0][4])==inf?-1:i);
}
}