Codeforces750 E. New Year and Old Subsequence(dp,线段树,floyd)

题意:

如果一个字符串,包含子序列2017,但是不包含子序列2016,就称这个字符串是好的。
给定长度为n的字符串,q次询问,
每次询问给出L,R,问子串[L,R]最少删掉多少个字符能成为好串,如果不能成为输出-1。

数据范围:n,q<=2e5

解法:
定义几个状态:
0.1.子序列2
2.子序列20
3.子序列201
4.子序列2017

我们先不考虑对于某个子区间的询问,而是考虑整个序列[1,n]如何进行dp
定义dp数组d[i][5],d[i][j]表示前i个数,只存在状态j的最小操作次数.
第一维是可以优化掉的,因此用d[i]表示当前只存在状态i的最小操作次数.
从左到右dp:
1.遇到数字2:
d[0]=d[0]+1
2.遇到数字0:
d[1]=min(d[0],d[1]+1)
3.遇到数字1:
d[2]=min(d[1],d[2]+1)
4.遇到数字7:
d[3]=min(d[2],d[3]+1)
5.遇到数字6:
d[3]=min(d[2],d[3]+1)
d[4]=min(d[3],d[4]+1)
根据题目要求,遇到数字6的时候,显然不能存在状态3和状态4

可以抽象为图论模型:
1.遇到数字2:
d[0]->d[0],边权为1,即维持d[0]需要1的操作次数.
d[0]->d[1],边权为0,因为加入2之后状态0就变成状态1.
左边的d[]表示前一轮dp数组,右边的dp[]表示加入当前数之后的dp数组
其实不将dp数组降维更好理解:
d[i-1][0]->d[i][0],边权为1,
d[i-1][0]->d[i][1],边权为0,
2.遇到其他数字同理.

发现遍历的过程中,每个位置其实就是几条有向边,
然后在之前的dp数组上加入这几条边再跑一遍最短路得到新的dp数组

因为状态只有[0,4],一共5,那么可以用一个矩阵g[5][5]存储当前的几条有向边,

那么要计算遍历完之后的dp数组(姑且叫做[1,n]的dp数组)
其实就是在初始dp数组上依次加上1到n号位置的有向边,然后跑最短路.
那么要计算[l,r]的dp数组,其实就是在初始dp数组上依次跑l到r号位置的有向边,然后跑最短路.
一个显然的性质:[l,r]拆分为[l,x],[x+1,r],
如果先计算出[x+1,r]的最短路,然后在[l,x]的结果上跑这个最短路,是不影响答案的.

那么要计算[l,r]的dp数组,就是在初始dp数组上直接跑[l,r]的最短路,不用依次了.
问题变为给定区间端点l,r,如何快速得到[l,r]的最短路矩阵.
因为是区间合并问题,所以可以用线段树来维护区间的最短路矩阵,
合并两个最短路矩阵就是对两个矩阵跑一遍floyd.(任意点对的最短路得用floyd求)

因为初始dp数组是空的,所以[l,r]的答案dp数组就是[l,r]的最短路矩阵
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=2e5+5;
struct Node{
    int a[5][5];
};
Node a[maxm<<2];
char s[maxm];
int n,q;
Node floyd(Node a,Node b){
    Node ans;
    for(int i=0;i<5;i++){
        for(int j=0;j<5;j++){
            ans.a[i][j]=1e9;
        }
    }
    for(int k=0;k<5;k++){
        for(int i=0;i<5;i++){
            for(int j=0;j<5;j++){
                ans.a[i][j]=min(ans.a[i][j],a.a[i][k]+b.a[k][j]);
            }
        }
    }
    return ans;
}
void pushup(int node){
    a[node]=floyd(a[node*2],a[node*2+1]);
}
void build(int l,int r,int node){
    if(l==r){
        for(int i=0;i<5;i++){
            for(int j=0;j<5;j++){
                a[node].a[i][j]=(i==j?0:1e9);
            }
        }
        if(s[l]=='2'){
            a[node].a[0][0]=1;
            a[node].a[0][1]=0;
        }else if(s[l]=='0'){
            a[node].a[1][1]=1;
            a[node].a[1][2]=0;
        }else if(s[l]=='1'){
            a[node].a[2][2]=1;
            a[node].a[2][3]=0;
        }else if(s[l]=='7'){
            a[node].a[3][3]=1;
            a[node].a[3][4]=0;
        }else if(s[l]=='6'){
            a[node].a[3][3]=1;
            a[node].a[4][4]=1;
        }
        return ;
    }
    int mid=(l+r)/2;
    build(l,mid,node*2);
    build(mid+1,r,node*2+1);
    pushup(node);
}
Node ask(int st,int ed,int l,int r,int node){
    if(st<=l&&ed>=r){
        return a[node];
    }
    int mid=(l+r)/2;
    if(ed<=mid)return ask(st,ed,l,mid,node*2);
    else if(st>mid)return ask(st,ed,mid+1,r,node*2+1);
    else return floyd(ask(st,mid,l,mid,node*2),ask(mid+1,ed,mid+1,r,node*2+1));
}
signed main(){
    scanf("%d%d",&n,&q);
    scanf("%s",s+1);
    build(1,n,1);
    while(q--){
        int l,r;scanf("%d%d",&l,&r);
        int ans=ask(l,r,1,n,1).a[0][4];
        if(ans==1e9)ans=-1;
        printf("%d\n",ans);
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值