The 2019 Asia Nanchang Online Programming Contest C. Hello 2019

题目

给一个 2e5 的字符串,有 2e5 的询问。每次询问一个区间【l,r】,问区间里至少删除多少个字符才能保证不含子串 “8102”,但是要含 “9201”。如果不能满足要求输出 -1。

分析

其实这个题是之前 cf 原题的改编,​​CF #750E​​ cf 原题大意是不含 “2016”,要含“2017”。先考虑简单 cf 版本的:

如果是单次询问的话可以用区间 DP来做,因为要求删去字符数最小,可以将删去一个字符代价看作 1。

定义状态 0 1 2 3 4 表示 “”,“2”,“20”,“201”,“2017”。对于每个位置的状态转移用矩阵表示:

The 2019 Asia Nanchang Online Programming Contest C. Hello 2019(区间DP + 线段树分治 + 矩阵)_状态转移


其中

The 2019 Asia Nanchang Online Programming Contest C. Hello 2019(区间DP + 线段树分治 + 矩阵)_状态转移_02

表示从

The 2019 Asia Nanchang Online Programming Contest C. Hello 2019(区间DP + 线段树分治 + 矩阵)_子串_03

状态转移到

The 2019 Asia Nanchang Online Programming Contest C. Hello 2019(区间DP + 线段树分治 + 矩阵)_子串_04

举个例子,假设当前位置是 2,那么矩阵为:

The 2019 Asia Nanchang Online Programming Contest C. Hello 2019(区间DP + 线段树分治 + 矩阵)_初始化_05

因为从 “” 到 “2” 不需要代价,反而从 “” 保持 “” 需要删去当前字符 2 。当前字符是 0、1、7 同理。

当前字符是 6 时,为了满足题目要求。那么状态 3、4 (带有 201)保持原来状态需要花费1 代价。即:

The 2019 Asia Nanchang Online Programming Contest C. Hello 2019(区间DP + 线段树分治 + 矩阵)_子串_06

上面是区间为 1 矩阵的情况,对于两个区间合并这里重载矩阵乘法,我们知道矩阵乘法是:

The 2019 Asia Nanchang Online Programming Contest C. Hello 2019(区间DP + 线段树分治 + 矩阵)_状态转移_07

这里因为要求最小代价,所以改成:

The 2019 Asia Nanchang Online Programming Contest C. Hello 2019(区间DP + 线段树分治 + 矩阵)_子串_08

上面其实就是区间 DP 的状态转移了,可以类比 Floyd 的思想。

这样就解决了单次询问,对于多次询问,可以用线段树区间查询分治来做,也就是维护每个一区间的矩阵状态。(因为区间DP支持区间合并)

这次南昌的题可以倒过来看,相应的区间也要倒过来。就转化为了 cf 的原题。

扩展:增加单点修改,和每个位置对应的代价,求最小代价。


cf 题目

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define fuck(x) cout << (x) << endl
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
using namespace std;
typedef long long ll;
const ll mod = 998244353;
const int N = 2e5 + 5;

int n, q;
char s[N];

struct mat{
int a[5][5];
mat() { memset(a, INF, sizeof(a)); }
mat operator * (const mat& b) const{
mat c;
for(int i = 0; i < 5; i++)
for (int j = 0; j < 5; j++)
for(int k = 0; k < 5; k++)
c.a[i][j] = min(c.a[i][j], a[i][k] + b.a[k][j]);
return c;
}
} tr[N<<2]; // 线段树维护区间矩阵状态。

void build(int l, int r, int rt){ // 线段树初始化也就是 DP 矩阵初始化
if(l == r){
for(int i = 0; i < 5; i++)
tr[rt].a[i][i] = 0;
if(s[l] == '2') tr[rt].a[0][0] = 1, tr[rt].a[0][1] = 0;
if(s[l] == '0') tr[rt].a[1][1] = 1, tr[rt].a[1][2] = 0;
if(s[l] == '1') tr[rt].a[2][2] = 1, tr[rt].a[2][3] = 0;
if(s[l] == '7') tr[rt].a[3][3] = 1, tr[rt].a[3][4] = 0;
if(s[l] == '6') tr[rt].a[3][3] = 1, tr[rt].a[4][4] = 1;
return;
}
int m = l + r >> 1;
build(lson);
build(rson);
tr[rt] = tr[rt << 1] * tr[rt << 1 | 1]; // pushup
}

mat query(int l, int r, int rt, int L, int R){ // 区间查询
if(L <= l && r <= R) return tr[rt];
int m = l + r >> 1;
if(R <= m) // 全在左边
return query(lson, L, R);
if(m < L) // 全在右边
return query(rson, L, R);
return query(lson, L, R) * query(rson, L, R);
}

int main(){
scanf("%d%d %s", &n, &q, s+1);
build(1, n, 1);
for (int i = 1, x, y; i <= q; i++){
scanf("%d%d", &x, &y);
mat ans = query(1, n, 1, x, y);
int res = ans.a[0][4] > n ? -1 : ans.a[0][4];
printf("%d\n", res);
}
return 0;
}

南昌题目:

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define fuck(x) cout << (x) << endl
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
using namespace std;
typedef long long ll;
const ll mod = 998244353;
const int N = 2e5 + 5;

int n, q;
char s[N], t[N];

struct mat{
int a[5][5];
mat() { memset(a, INF, sizeof(a)); }
mat operator * (const mat& b) const{
mat c;
for(int i = 0; i < 5; i++)
for (int j = 0; j < 5; j++)
for(int k = 0; k < 5; k++)
c.a[i][j] = min(c.a[i][j], a[i][k] + b.a[k][j]);
return c;
}
} tr[N<<2];

void build(int l, int r, int rt){
if(l == r){
for(int i = 0; i < 5; i++)
tr[rt].a[i][i] = 0;
if(s[l] == '2') tr[rt].a[0][0] = 1, tr[rt].a[0][1] = 0;
if(s[l] == '0') tr[rt].a[1][1] = 1, tr[rt].a[1][2] = 0;
if(s[l] == '1') tr[rt].a[2][2] = 1, tr[rt].a[2][3] = 0;
if(s[l] == '9') tr[rt].a[3][3] = 1, tr[rt].a[3][4] = 0;
if(s[l] == '8') tr[rt].a[3][3] = 1, tr[rt].a[4][4] = 1;
return;
}
int m = l + r >> 1;
build(lson);
build(rson);
tr[rt] = tr[rt << 1] * tr[rt << 1 | 1]; // pushup
}

mat query(int l, int r, int rt, int L, int R){
if(L <= l && r <= R)
return tr[rt];
int m = l + r >> 1;
if(R <= m) // 全在左边
return query(lson, L, R);
if(m < L) // 全在右边
return query(rson, L, R);
return query(lson, L, R) * query(rson, L, R);
}

int main(){
scanf("%d%d %s", &n, &q, t+1);
for(int i = 1; i <= n; i++){
s[i] = t[n - i + 1];
}
build(1, n, 1);
for (int i = 1, x, y; i <= q; i++){
scanf("%d%d", &x, &y);
mat ans = query(1, n, 1, n-y+1, n-x+1);
int res = ans.a[0][4] > n ? -1 : ans.a[0][4];
printf("%d\n", res);
}
return 0;
}