New Year and Old Subsequence CodeForces - 750E(线段树 + 矩阵)

New Year and Old Subsequence (CodeForces - 750E)

题意:

给出一个长度为\(N\)的数字串,\(q\)次询问。每次询问一段区间。在区间内删除尽量少的字符,使得区间内含有序列"\(2017\)",且不含有"\(2016\)"。

\(n,q<=200000\)。

题解:

用\(01234\)五种状态分别表示""、 "\(2\)"、"\(20\)"、"\(201\)"、"\(2017\)"。

设矩阵\(M[5][5]\),\(M[i][j]\)表示从状态\(i\)转移到\(j\)需要删除的最少字符数量。

例如,\('2'\)可以把状态\(0\)转移到状态\(1\),所以矩阵就是

\[a=\left[
\begin{matrix}
1 & 0 & \inf & \inf & \inf \\inf & 0 & \inf & \inf & \inf \\inf & \inf & 0 & \inf & \inf \\inf & \inf & \inf & 0 & \inf \\inf & \inf & \inf & \inf & 0
\end{matrix}
\right]\]

代表,如果我们删去这个\('2'\),维持本来的状态\(0\),花费就是\(1\)。如果不删,那么状态\(0\)就可以转移到状态\(1\),所以\(a[0][1] = 0\)。

对于\('0'\ '1'\ '7'\)同理可构造类似的矩阵。

对于\('6'\),可以构造如下矩阵

\[a=\left[
\begin{matrix}
0 & \inf & \inf & \inf & \inf \\inf & 0 & \inf & \inf & \inf \\inf & \inf & 0 & \inf & \inf \\inf & \inf & \inf & 1 & \inf \\inf & \inf & \inf & \inf & 1
\end{matrix}
\right]\]

表示,如果之前有"\(201\)" 或者 "\(2017\)",这个\('6'\)就需要删除。否则就不用删除。

对于两个矩阵的合并,我们只要像\(Floyd\)算法那样,枚举转移就可以了。

因为矩阵的状态转移是满足结合律的,所以用线段树维护区间,就可以多组查询了。

以后再遇到区间上的状态转移,可以尝试用这种方法瞎搞一搞。

代码

#include <bits/stdc++.h>
#define fopi freopen("in.txt", "r", stdin)
#define fopo freopen("out.txt", "w", stdout)
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 2e5 + 10;
typedef long long LL;

char s[maxn];

struct Matrix {
    int m[5][5];
    Matrix() { memset(m, 0x3f, sizeof(m)); }
    Matrix operator * (const Matrix &rhs) {
        Matrix res;
        for (int k = 0; k <= 4; k++)
        for (int i = 0; i <= 4; i++)
        for (int j = 0; j <= 4; j++)
            res.m[i][j] = min(res.m[i][j], m[i][k] + rhs.m[k][j]);
        return res;
    }
};

struct SegTree {
    struct Node {
        int l, r;
        Matrix M;
    }t[maxn * 4];

    void build(int id, int l, int r) {
        t[id].l = l, t[id].r = r;
        if (l == r) {
            for (int i = 0; i <= 4; i++) t[id].M.m[i][i] = 0;
            if (s[l] == '2') { t[id].M.m[0][0] = 1; t[id].M.m[0][1] = 0; }
            if (s[l] == '0') { t[id].M.m[1][1] = 1; t[id].M.m[1][2] = 0; }
            if (s[l] == '1') { t[id].M.m[2][2] = 1; t[id].M.m[2][3] = 0; }
            if (s[l] == '7') { t[id].M.m[3][3] = 1; t[id].M.m[3][4] = 0; }
            if (s[l] == '6') { t[id].M.m[3][3] = 1; t[id].M.m[4][4] = 1; }
            return;
        }
        int mid = (l+r) / 2;
        build(id*2, l, mid);
        build(id*2+1, mid+1, r);
        t[id].M = t[id*2].M * t[id*2+1].M;
    }

    Matrix query(int id, int l, int r) {
        if (l <= t[id].l && r >= t[id].r) return t[id].M;
        int mid = (t[id].l + t[id].r) / 2;
        if (r <= mid) return query(id*2, l, r);
        else if (l > mid) return query(id*2+1, l, r);
        return query(id*2, l, mid) * query(id*2+1, mid+1, r);
    }
}T;

int l, r, n, q;
int main() {
    //fopi;
    scanf("%d%d", &n, &q);
    scanf("%s", s+1);
    T.build(1, 1, n);
    for (int i = 1; i <= q; i++) {
        scanf("%d%d", &l, &r);
        int res = T.query(1, l, r).m[0][4];
        printf("%d\n", res >= inf ? -1 : res);
    }
}

原文地址:https://www.cnblogs.com/ruthank/p/11506185.html

时间: 2024-10-13 16:41:32

New Year and Old Subsequence CodeForces - 750E(线段树 + 矩阵)的相关文章

Codeforces 384E 线段树+dfs序

题目链接:点击打开链接 题意: 给定n个点,m个询问的无向树(1为根) 下面n个数表示每个点的权值 下面n-1行给出树 操作1:x点权值+v, x的第 i & 1 的儿子-v, 第 !(i&1) 的儿子+v 操作2:询问x点权值 dfs把树转成序列 根据深度把点分成2组 分别用线段树维护.. 然后Y一下 #include<stdio.h> #include<string.h> #include<iostream> #include<algorith

Codeforces 446C 线段树 递推Fibonacci公式

聪哥推荐的题目 区间修改和区间查询,但是此题新颖之处就在于他的区间修改不是个定值,而是从L 到 R 分别加 F1.F2....Fr-l+1 (F为斐波那契数列) 想了一下之后,觉得用fib的前缀和来解决,每次做懒惰标记记录下当前区间是从哪个L开始加起的,敲了一半之后发现有问题,就跟上次遇到的懒惰标记问题一样,这是个覆盖性的懒惰标记,每次向下传递后,都要先清除孩子的,清除孩子的也有可能要清除son's son,所以要一直pushdown下去,否则就会错,但这样就会超时. 能不能有个累加型的标记让我

Bash and a Tough Math Puzzle CodeForces 914D 线段树+gcd数论

Bash and a Tough Math Puzzle CodeForces 914D 线段树+gcd数论 题意 给你一段数,然后小明去猜某一区间内的gcd,这里不一定是准确值,如果在这个区间内改变一个数的值(注意不是真的改变),使得这个区间的gcd是小明所猜的数也算小明猜对.另一种操作就是真的修改某一点的值. 解题思路 这里我们使用线段树,维护区间内的gcd,判断的时候需要判断这个区间的左右子区间的gcd是不是小明猜的数的倍数或者就是小明猜的数,如果是,那么小明猜对了.否则就需要进入这个区间

ZOJ 3772 Calculate the Function 线段树+矩阵

Calculate the FunctionTime Limit:2000MS     Memory Limit:65536KB     64bit IO Format:%lld & %llu Submit Status Appoint description:  System Crawler  (2014-04-09) Description You are given a list of numbers A1A2 .. AN and M queries. For the i-th query

Codeforces 833B 线段树优化 dp

Codeforces  833B  The Bakery 题意: n 个数要分成 k 块,每块的价值是其不同数的个数,问价值和最大是多少. tags: dp[i][j]表示前 j 个数分成 i 块的最大权值和,转移: dp[i][j] = max( dp[i-1][k] + val[k+1][j] ) , k是 1~j . 但这个过程其实并不好转移,要利用累加的特点,用线段树进行优化 (感觉我不看题解是想不到的,2333) 大概就是,对于第 i 层,我们假定已经知道了第 i-1 层,也就是求出了

Codeforces 938G 线段树分治 线性基 可撤销并查集

Codeforces 938G Shortest Path Queries 一张连通图,三种操作 1.给x和y之间加上边权为d的边,保证不会产生重边 2.删除x和y之间的边,保证此边之前存在 3.询问x到y的路径异或最小值 保证图在任意时刻连通 首先连通图路径异或相当于从x到y的任意一条路径再异或上若干个环得到的,只要在dfs过程中把非树边成的环丢到线性基里就好了,其他环一定可以通过这些环异或组合出来 有加边删边操作怎么做呢?线段树时间分治!注意到不能保证在线段树的任意一个节点图是连通的,需要用

codeforces 589G:线段树+二分

离线做 先按照t[]建线段树,维护区间的有效天数以及可用时间 然后把t[]从小到达排序,记得记录t中元素在线段树中的位置 把询问按照d从小到大排序,依次考虑 由于按照d排序,所以当前询问肯定是d最短的,我们在t数组中找到大于当前d的第一个元素位置,把这之前的元素全部从线段树中删除,表示这些天数无效 因为当前d已经是最小,所以删除对后续不会有影响 二分一个天数,查找0~mid天一共的工作时间(工作时间=总可用时间-准备时间*有效天数) #include"cstdio" #include&

CodeForces 343D 线段树维护dfs序

给定一棵树,初始时树为空 操作1,往某个结点注水,那么该结点的子树都注满了水 操作2,将某个结点的水放空,那么该结点的父亲的水也就放空了 操作3,询问某个点是否有水 我们将树进行dfs, 生成in[u], 访问结点u的时间戳,out[u],离开结点u的时间戳 每个结点的in值对应在线段树中的区间的一点 那么对于操作1, 只要将区间[in[u],out[u]] 的值都改为1, 但是如果区间[in[u],out[u]] 原先存在为0的点,那么父区间肯定是空的,这个操作不能 改变父区间的状态,所以需要

Codeforces 558E 线段树处理字符串内排序

给出长度为n的字符串,m个操作. 每一个操作有三个值 l,r,op. op==1,表示将字符串中[ l ,r ]的部分依照升序排列. op==0,表示将字符串中[ l ,r ]的部分依照降序排列. 输出终于的字符串 按小写字母建26颗线段树 对于每次改动,先记录[l,r]区间内各个字母出现的次数,并对对应区间清空,然后依照升序或者降序从新更新 #include "stdio.h" #include "string.h" char str[100010]; int n