天天看点

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

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”。对于每个位置的状态转移用矩阵表示:

其中

表示从

状态转移到

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

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

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

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

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

上面其实就是区间 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;
}