【动态规划】【动态 DP】 CF750E New Year and Old Subsequence
题目描述
定义数字串是好的当且仅当其包含子序列 2017 ,不包含子序列 2016。
定义数字串的丑陋值为最少删掉几个字符,它才能是好的,如果一直不能,就是 \(-1\) 。
给定数字串 \(t\) ,长度为 \(n\) ,\(q\) 次询问求 \([l,r]\) 的丑陋值。
\(1 \leq n,q \leq 2 \times 10^5\) 。
算法描述
对于这种问题,我们先探究如果给你一个数字串,怎样求丑陋值。
我们发现由于是子序列,情况太复杂,没办法讨论,考虑 dp。每个点的状态定义为在其之前凑出关键字串的 “进度”,\(f_{i,0/1/2/3/4}\) 表示前 \(i\) 个字符,凑出 \(\emptyset/2/20/201/2017\) 且没有 \(2016\) 的最少删除个数,可以分讨:
\[f_{i,0} = f_{i - 1,0} + [s_i = 2]
\]
\[f_{i,1} = \min(f_{i - 1,0} \times [s_i = 2],f_{i - 1,1} + [s_i = 0])
\]
注意这里的 \(\times\) 不是乘法,而是如果 \(s_i = 2\) 就有这一项,不等于这一项就是 inf。
\[f_{i,2} = \min(f_{i - 1,1} \times [s_i = 0],f_{i - 1,2} + [i = 1])
\]
\[f_{i,3} = \min(f_{i - 1,2} \times [s_i = 1],f_{i - 1,3} + [s_i = 7 | s_i = 6])
\]
\[f_{i,4} = \min(f_{i - 1,3} \times [s_i = 7],f_{i - 1,4} + [s_i = 6])
\]
这样可以 \(\Theta(n)\) 求一个序列的丑陋值。
那么怎样回答区间呢?
考虑用 DDP 的思路转化成矩阵,再套上线段树。这里采用 \(\min,+\) 的广义矩阵,将 \([f_{i,0},f_{i,1},f_{i,2},f_{i,3},f_{i,4}]\) 作为一个矩阵,手模可以得到一个 \(5 \times 5\) 的转移矩阵:
\[\begin{bmatrix}
[s_i = 2] & 0 \times [s_i = 2] & inf & inf & inf\\
inf & [s_i = 0] & 0 \times [s_i = 0] & inf & inf\\
inf & inf & [s_i = 1] & 0 \times [s_i = 1] & inf\\
inf & inf & inf & [s_i = 7 | s_i = 6] & 0 \times [s_i = 7]\\
inf & inf & inf & inf & [s_i = 6]
\end{bmatrix}
\]
线段树上每个点初始化,区间询问将其乘起来,再左乘上初始矩阵 \([0,inf,inf,inf,inf]\) 即可。
时间复杂度 \(\Theta(k^3n\log n)\) 。
这题可以说是最简单的 DDP 转化,由此看出如果涉及修改的话直接单点改某一个矩阵即可,复杂度 \(\Theta(k^3\log n)\) 的,十分优秀。
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5,inf = 0x3f3f3f3f;
struct Matrix{
int a[5][5];
};
Matrix operator *(Matrix x,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;
}
int n,q;
char s[N];
struct Segment_Tree{
Matrix a[N << 2];
inline void pushup(int pos) {a[pos] = a[pos << 1] * a[pos << 1 | 1];}
inline void build(int l,int r,int pos)
{
if(l == r)
{
for(int i = 0;i < 5;i++)
for(int j = 0;j < 5;j++)
{
if(i == 0 && j == 0) a[pos].a[i][j] = (s[l] == '2');
else if(i == 1 && j == 1) a[pos].a[i][j] = (s[l] == '0');
else if(i == 2 && j == 2) a[pos].a[i][j] = (s[l] == '1');
else if(i == 3 && j == 3) a[pos].a[i][j] = (s[l] == '7') | (s[l] == '6');
else if(i == 4 && j == 4) a[pos].a[i][j] = (s[l] == '6');
else if(i == 0 && j == 1) a[pos].a[i][j] = ((s[l] == '2') ? 0 : inf);
else if(i == 1 && j == 2) a[pos].a[i][j] = ((s[l] == '0') ? 0 : inf);
else if(i == 2 && j == 3) a[pos].a[i][j] = ((s[l] == '1') ? 0 : inf);
else if(i == 3 && j == 4) a[pos].a[i][j] = ((s[l] == '7') ? 0 : inf);
else a[pos].a[i][j] = inf;
}
return;
}
int mid = (l + r) >> 1;
build(l,mid,pos << 1); build(mid + 1,r,pos << 1 | 1);
pushup(pos);
}
inline Matrix query(int l,int r,int L,int R,int pos)
{
if(L <= l && r <= R) return a[pos];
int mid = (l + r) >> 1;
if(L <= mid && R > mid) return query(l,mid,L,R,pos << 1) * query(mid + 1,r,L,R,pos << 1 | 1);
else if(L <= mid) return query(l,mid,L,R,pos << 1);
else if(R > mid) return query(mid + 1,r,L,R,pos << 1 | 1);
}
}t;
int main()
{
cin>>n>>q;
scanf("%s",s + 1);
t.build(1,n,1);
for(int i = 1,l,r;i <= q;i++)
{
cin>>l>>r;
Matrix now = t.query(1,n,l,r,1);
int ori[5] = {0,inf,inf,inf,inf},res[5] = {inf,inf,inf,inf,inf};
for(int j = 0;j < 5;j++)
for(int k = 0;k < 5;k++)
res[j] = min(res[j],ori[k] + now.a[k][j]);
printf("%d\n",(res[4] == inf) ? -1 : res[4]);
}
return 0;
}