Description
给定一个字符串,每个字符都是 0 − 9 0-9 0−9之间的数。
每次查询在一段区间 [ l , r ] [l,r] [l,r]中至少需要删掉多少个字符,才能使得剩下的字符串不包含子序列2016且包含子序列2017。
Solution
考虑 d p dp dp。
d p i , j : dp_{i,j}: dpi,j: 目前匹配到了串2017的第 j j j位的最少删去字符数量。
状态转移如下:
①若
a
i
=
2
a_i=2
ai=2。
(1)不删去
a
i
a_i
ai。此时
a
i
a_i
ai的作用在于将一个暂未匹配的状态(
∅
∅
∅)变为一个匹配
1
1
1位的状态(“2”)。在这种决策下有
d
p
i
,
1
=
d
p
i
−
1
,
0
dp_{i,1}=dp_{i-1,0}
dpi,1=dpi−1,0。
(2)删去
a
i
a_i
ai。可以发现删除
a
i
a_i
ai起到了保留状态的作用,可以不改变一个匹配
0
0
0位的状态。在这种决策下有
d
p
i
,
0
=
d
p
i
−
1
,
0
+
1
dp_{i,0}=dp_{i-1,0}+1
dpi,0=dpi−1,0+1。
②若
a
i
=
0
a_i=0
ai=0,与①同理:
(1)
d
p
i
,
2
dp_{i,2}
dpi,2的决策点包含
d
p
i
−
1
,
1
dp_{i-1,1}
dpi−1,1;
(2)
d
p
i
,
1
dp_{i,1}
dpi,1的决策点包含
d
p
i
−
1
,
1
+
1
dp_{i-1,1}+1
dpi−1,1+1。
③若
a
i
=
1
/
7
a_i=1/7
ai=1/7,与①②同理,请自行推导。
④若
a
i
=
6
a_i=6
ai=6:
(1)不删去
a
i
a_i
ai。这种转移合法当且仅当当前匹配到的位数不超过
2
2
2,否则转移不合法。为什么这里有一个状态转移的合法限制呢?假设匹配了
3
3
3位,那么此时就含有子序列
201
201
201,添上一个
6
6
6后就存在了子序列
2016
2016
2016而这时题目严格禁止出现的。匹配了
4
4
4位的情况同理。所以,
d
p
i
,
0
,
d
p
i
,
1
,
d
p
i
,
2
dp_{i,0},dp_{i,1},dp_{i,2}
dpi,0,dpi,1,dpi,2可以直接转移过来,但
d
p
i
,
3
,
d
p
i
,
4
dp_{i,3},dp_{i,4}
dpi,3,dpi,4必须分别从
d
p
i
−
1
,
3
,
d
p
i
−
1
,
4
dp_{i-1,3},dp_{i-1,4}
dpi−1,3,dpi−1,4加
1
1
1而转移过来。
⑤若 a i a_i ai为其他的数,那么不会有任何影响,直接转移过来即可。
对于每次询问,扫一遍整个区间进行转移,时间复杂度为 O ( 5 2 q n ) O(5^2\ qn) O(52 qn),无法通过。
这一类含有“区间查询”且不含区间修改的 d p dp dp是经典的动态 d p dp dp,即线段树维护矩乘的模型。
这句话说得有亿点绕?没关系,我们形象地解释一遍。
不难发现当前的状态是一个长度为 5 5 5的小数组,每经过一个位置就要经过对应的变换(转移)。这种变换可以转化为广义矩阵乘法的形式。
更具体地说,状态转移形如 d p i , j = d p i − 1 , p + c o s t ( p , j ) dp_{i,j}=dp_{i-1,p}+cost(p,j) dpi,j=dpi−1,p+cost(p,j),我们可以大胆地重定义矩阵乘法为 C i , j = min k = 1 n ( A i , k + B k , j ) C_{i,j}=\min_{k=1}^n (A_{i,k}+B_{k,j}) Ci,j=k=1minn(Ai,k+Bk,j)
显然这是有结合律的。对于每一个位置的对应状态转移可以压缩为一个矩阵,表示“当前”状态通过这个位置要乘上这个矩阵。
最后我们分析每次区间查询的本质: 等价于一次区间查询矩阵乘积。我们可以采用线段树维护区间矩乘,从而解决了本题。
时间复杂度为 O ( 5 3 n log n ) O(5^3\ n \log n) O(53 nlogn)。
Code
#include <bits/stdc++.h>
#define inf 2000000007
#define int long long
using namespace std;
const int maxl=200005;
int read(){
int s=0,w=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') w=-w;ch=getchar();}
while (ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+(ch^'0');ch=getchar();}
return s*w;
}
int n,q,le,ri;
int s[maxl];
struct Matrix{
int a[5][5];
}tree[maxl*4],b[maxl];
Matrix operator * (const Matrix &x,const Matrix &y){
Matrix z;
for (int i=0;i<5;i++){
for (int j=0;j<5;j++){
z.a[i][j]=inf;
for (int k=0;k<5;k++)
z.a[i][j]=min(z.a[i][j],x.a[i][k]+y.a[k][j]);
}
}
return z;
}
void pushup(int rt){tree[rt]=tree[2*rt]*tree[2*rt+1];};
void build_tree(int l,int r,int rt){
if (l==r){
tree[rt]=b[l];
return;
}
int mid=(l+r)>>1;
build_tree(l,mid,2*rt),build_tree(mid+1,r,2*rt+1);
pushup(rt);
}
Matrix query(int nl,int nr,int l,int r,int rt){
if (nl<=l&&r<=nr){
return tree[rt];
}
int mid=(l+r)>>1;Matrix res;
if (nl<=mid&&nr<=mid) res=query(nl,nr,l,mid,2*rt);
else if (nl>mid&&nr>mid) res=query(nl,nr,mid+1,r,2*rt+1);
else if (nl<=mid&&nr>mid) res=query(nl,nr,l,mid,2*rt)*query(nl,nr,mid+1,r,2*rt+1);
return res;
}
signed main(){
n=read(),q=read();
for (int i=1;i<=n;i++){
char tmpxl;
cin>>tmpxl;
s[i]=tmpxl-'0';
}
for (int i=1;i<=n;i++){
for (int j=0;j<5;j++){
for (int k=0;k<5;k++){
if (j!=k) b[i].a[j][k]=inf;
else b[i].a[j][k]=0;
}
}
if (s[i]==2) b[i].a[0][0]=1,b[i].a[0][1]=0;
if (s[i]==0) b[i].a[1][1]=1,b[i].a[1][2]=0;
if (s[i]==1) b[i].a[2][2]=1,b[i].a[2][3]=0;
if (s[i]==7) b[i].a[3][3]=1,b[i].a[3][4]=0;
if (s[i]==6) b[i].a[3][3]=1,b[i].a[4][4]=1;
}
build_tree(1,n,1);
while (q--){
le=read(),ri=read();
int ans=query(le,ri,1,n,1).a[0][4];
if (ans>ri-le+1) puts("-1");
else printf("%lld\n",ans);
}
return 0;
}