Description
给定一个字符串,每个字符都是0−90-90−9之间的数。
每次查询在一段区间[l,r][l,r][l,r]中至少需要删掉多少个字符,才能使得剩下的字符串不包含子序列2016且包含子序列2017。
Solution
考虑dpdpdp。
dpi,j:dp_{i,j}:dpi,j: 目前匹配到了串2017的第jjj位的最少删去字符数量。
状态转移如下:
①若ai=2a_i=2ai=2。
(1)不删去aia_iai。此时aia_iai的作用在于将一个暂未匹配的状态(∅∅∅)变为一个匹配111位的状态(“2”)。在这种决策下有dpi,1=dpi−1,0dp_{i,1}=dp_{i-1,0}dpi,1=dpi−1,0。
(2)删去aia_iai。可以发现删除aia_iai起到了保留状态的作用,可以不改变一个匹配000位的状态。在这种决策下有dpi,0=dpi−1,0+1dp_{i,0}=dp_{i-1,0}+1dpi,0=dpi−1,0+1。
②若ai=0a_i=0ai=0,与①同理:
(1)dpi,2dp_{i,2}dpi,2的决策点包含dpi−1,1dp_{i-1,1}dpi−1,1;
(2)dpi,1dp_{i,1}dpi,1的决策点包含dpi−1,1+1dp_{i-1,1}+1dpi−1,1+1。
③若ai=1/7a_i=1/7ai=1/7,与①②同理,请自行推导。
④若ai=6a_i=6ai=6:
(1)不删去aia_iai。这种转移合法当且仅当当前匹配到的位数不超过222,否则转移不合法。为什么这里有一个状态转移的合法限制呢?假设匹配了333位,那么此时就含有子序列201201201,添上一个666后就存在了子序列201620162016而这时题目严格禁止出现的。匹配了444位的情况同理。所以,dpi,0,dpi,1,dpi,2dp_{i,0},dp_{i,1},dp_{i,2}dpi,0,dpi,1,dpi,2可以直接转移过来,但dpi,3,dpi,4dp_{i,3},dp_{i,4}dpi,3,dpi,4必须分别从dpi−1,3,dpi−1,4dp_{i-1,3},dp_{i-1,4}dpi−1,3,dpi−1,4加111而转移过来。
⑤若aia_iai为其他的数,那么不会有任何影响,直接转移过来即可。
对于每次询问,扫一遍整个区间进行转移,时间复杂度为O(52 qn)O(5^2\ qn)O(52 qn),无法通过。
这一类含有“区间查询”且不含区间修改的dpdpdp是经典的动态dpdpdp,即线段树维护矩乘的模型。
这句话说得有亿点绕?没关系,我们形象地解释一遍。
不难发现当前的状态是一个长度为555的小数组,每经过一个位置就要经过对应的变换(转移)。这种变换可以转化为广义矩阵乘法的形式。
更具体地说,状态转移形如dpi,j=dpi−1,p+cost(p,j)dp_{i,j}=dp_{i-1,p}+cost(p,j)dpi,j=dpi−1,p+cost(p,j),我们可以大胆地重定义矩阵乘法为Ci,j=mink=1n(Ai,k+Bk,j)C_{i,j}=\min_{k=1}^n (A_{i,k}+B_{k,j})Ci,j=k=1minn(Ai,k+Bk,j)
显然这是有结合律的。对于每一个位置的对应状态转移可以压缩为一个矩阵,表示“当前”状态通过这个位置要乘上这个矩阵。
最后我们分析每次区间查询的本质: 等价于一次区间查询矩阵乘积。我们可以采用线段树维护区间矩乘,从而解决了本题。
时间复杂度为O(53 nlogn)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;
}

225

被折叠的 条评论
为什么被折叠?



