New Year and Old Subsequence【矩阵+线段树+DP】
题意:
给出一串数字序列,每次询问对于子串 \([l,r]\) 至少删除多少个数字才能保证该子串无子序列 \('2016'\) 但有子序列 \('2017'\)。
\(4 ≤ n ≤ 200 000, 1 ≤ q ≤ 200 000\)
题目链接:https://codeforces.com/contest/750/problem/E
分析:
分别用数字表示以下状态:
\(0:\emptyset\)
\(1:'2'\)
\(2:'20'\)
\(3:'201'\)
\(4:'2017'\)
构造一个 \(5\times 5\) 的转移矩阵,\(mat[i][j]\) 表示 \(i\) 状态是否可以转移到 \(j\) 状态,如果可以则保存最小花费。而且,二者之间可以通过一个中间状态 \(k\) 来转换。通过矩阵的加法来去最小。
对于转换矩阵先初始为:除正对角线上为 \(0\),其余都为 \(inf\)。因为初始的时候状态 \(i\) 只能转换到状态 \(i\) 不需要删字符,转换到其他状态都是不可达的。
遍历到第 \(x\) 个字符 \(s[x]\) 时,对 \(s[x]\) 的情况进行讨论:
s[x]=‘2’:字符 \(2\) 只会影响状态 \(0\)→状态 \(1\) 的过程,因此由状态 \(0\) ->状态 \(1\) 不需要删字符,即 \(mat[0][1]=0\),但由状态 \(0\)->状态 \(0\),已经被破坏,因此为了维持状态 \(0\)->状态
\(0\) ,需要将字符 \(2\) 删除的结果存放在转换矩阵中,即 \(mat[0][0]=1\);而剩下的状态都不能加上字符 \(2\) 变成其余状态,故都为 \(inf\)。
s[x]=‘0’:字符 \(0\) 会影响状态 \(1\)->状态 \(2\) 过程,字符 \(0,1,7\) 道理都一样
s[x]=‘1’
s[x]=‘7’
s[x]=‘6’:对于字符 \(6\),由于不能包含 \(2016\),因此对于维持状态 \(3(‘201’)\)->状态 \(3(‘201’)\),需删掉 \(6\),故 \(mat[3][3]=1\);对于状态 \(4(‘2017’)\),即若不删 \(6\),则会包含 \(2016\),因此要删去,即 \(mat[4][4]=1\),其余情况不做处理。
过程和 \(AC\) 自动机上跑 \(DP\) 类似,具体见代码。
代码:
#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=2e5+5;
struct matrix
{
int mat[5][5];
void init()
{
for(int i=0;i<5;i++)
for(int j=0;j<5;j++)
mat[i][j]=inf;
}
matrix operator +(const matrix &b)const
{
matrix res;
for(int i=0;i<5;i++)
{
for(int j=0;j<5;j++)
{
res.mat[i][j]=inf;
for(int k=0;k<5;k++)
res.mat[i][j]=min(res.mat[i][j],mat[i][k]+b.mat[k][j]);
}
}
return res;
}
}tree[N<<2];
char ss[N];
void build(int l,int r,int rt)
{
if(l==r)
{//构造转移矩阵:
tree[rt].init();
for(int i=0;i<5;i++) tree[rt].mat[i][i]=0;
if(ss[l]=='2') tree[rt].mat[0][0]=1,tree[rt].mat[0][1]=0;
if(ss[l]=='0') tree[rt].mat[1][1]=1,tree[rt].mat[1][2]=0;
if(ss[l]=='1') tree[rt].mat[2][2]=1,tree[rt].mat[2][3]=0;
if(ss[l]=='7') tree[rt].mat[3][3]=1,tree[rt].mat[3][4]=0;
if(ss[l]=='6') tree[rt].mat[4][4]=1,tree[rt].mat[3][3]=1;
return;
}
int mid=(l+r)>>1;
build(l,mid,rt<<1);
build(mid+1,r,rt<<1|1);
tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
matrix query(int l,int r,int L,int R,int rt)
{
if(L<=l&&r<=R)
return tree[rt];
int mid=(l+r)>>1;
matrix res;
res.init();
for(int i=0;i<5;i++) res.mat[i][i]=0;
if(L<=mid) res=res+query(l,mid,L,R,rt<<1);
if(R>mid) res=res+query(mid+1,r,L,R,rt<<1|1);
return res;
}
int main()
{
int n,q,l,r;
scanf("%d%d",&n,&q);
scanf("%s",ss+1);
build(1,n,1);
while(q--)
{
scanf("%d%d",&l,&r);
matrix ans=query(1,n,l,r,1);
printf("%d\n",ans.mat[0][4]==inf?-1:ans.mat[0][4]);
}
return 0;
}