【动态规划】【动态 DP】 CF750E New Year and Old Subsequence

发布时间 2023-11-10 17:04:54作者: The_Last_Candy

题目描述

定义数字串是好的当且仅当其包含子序列 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;
}