New Year and Old Subsequence (线段树优化dp)

22 篇文章 0 订阅

New Year and Old Subsequence

time limit per test

3 seconds

memory limit per test

256 megabytes

input

standard input

output

standard output

A string t is called nice if a string "2017" occurs in t as a subsequence but a string "2016" doesn't occur in t as a subsequence. For example, strings "203434107" and "9220617" are nice, while strings "20016", "1234" and "20167" aren't nice.

The ugliness of a string is the minimum possible number of characters to remove, in order to obtain a nice string. If it's impossible to make a string nice by removing characters, its ugliness is  - 1.

Limak has a string s of length n, with characters indexed 1 through n. He asks you q queries. In the i-th query you should compute and print the ugliness of a substring (continuous subsequence) of s starting at the index ai and ending at the index bi (inclusive).

Input

The first line of the input contains two integers n and q (4 ≤ n ≤ 200 000, 1 ≤ q ≤ 200 000) — the length of the string s and the number of queries respectively.

The second line contains a string s of length n. Every character is one of digits '0'–'9'.

The i-th of next q lines contains two integers ai and bi (1 ≤ ai ≤ bi ≤ n), describing a substring in the i-th query.

Output

For each query print the ugliness of the given substring.

Examples

input

Copy

8 3
20166766
1 8
1 7
2 8

output

Copy

4
3
-1

input

Copy

15 5
012016662091670
3 4
1 14
4 15
1 13
10 15

output

Copy

-1
2
1
-1
-1

input

Copy

4 2
1234
2 4
1 2

output

Copy

-1
-1

Note

In the first sample:

  • In the first query, ugliness("20166766") = 4 because all four sixes must be removed.
  • In the second query, ugliness("2016676") = 3 because all three sixes must be removed.
  • In the third query, ugliness("0166766") =  - 1 because it's impossible to remove some digits to get a nice string.

In the second sample:

  • In the second query, ugliness("01201666209167") = 2. It's optimal to remove the first digit '2' and the last digit '6', what gives a string "010166620917", which is nice.
  • In the third query, ugliness("016662091670") = 1. It's optimal to remove the last digit '6', what gives a nice string "01666209170".

题目大意:给出一个字符串,m个查询,询问l,r内,至少删除多少个字符使得这个字符串只包含子序列2017 而不包含子序列2016。

解题思路:定义5个状态:

状态0: 什么也不包含

状态1:只包含2

状态2:只包含20

状态3:只包含201

状态4:只包含2017

设 dp(x,i,j)为前x个字符串 最多恰好包含2017的前i个字符,转移到j的最小花费。

然后可以得到dp转移方程。

但是题目是每次对于一个区间的查询,我们每次对一个区间做一次dp显然不太合理。

其实这个题目我还不太理解他的做法,只是明白一点点,记录一下我的理解。

这个题目显然可以用分治的方法。

即对于一个l,r。我分别求出(l,m)( m+r,r)各自从0状态转移到4状态的最小花费,这是一个5×5的矩阵。

然后我们合并两个矩阵即可(类似区间dp,枚举初始状态和末状态,然后枚举中间状态进行转移)。

关于矩阵初值:

对于叶子节点的n个矩阵我们先把对角线都为0,其他为inf。

        if(num[l]==2){t[rt].a[0][0]=1;t[rt].a[0][1]=0;}
        if(num[l]==0){t[rt].a[1][2]=0;t[rt].a[1][1]=1;}
        if(num[l]==1){t[rt].a[2][2]=1;t[rt].a[2][3]=0;}
        if(num[l]==7){t[rt].a[3][3]=1;t[rt].a[3][4]=0;}
        if(num[l]==6){t[rt].a[3][3]=1;t[rt].a[4][4]=1;}

这里比较重要,意思也很好理解。

如果当前是字符是2,那么我之前的状态0加上当前的字符2再转移到状态0的花费就是1.

同理,如果当前的字符是0,那么假设之前的状态1加上当前的字符0再转移到状态1的花费就是1(需要把当前或者之前的0删除)。

最有由于不能包含2016,当碰到6的时候,状态3和状态4转移回自己的花费就是1(此时需要删掉6).

上边即对应四种删的情况 分别是 2 0 1 6.

#include<bits/stdc++.h>
using namespace std;
#define LL long long

const int N = 2e5+5;
const int inf = 0x3f3f3f3f;

struct node
{
    int a[5][5];
    void init(){memset(a,inf,sizeof(a));}

}t[N*4];

node ans;

node Merge(node le,node ri)
{
    node tmp;
    tmp.init();
    for(int i=0;i<5;i++)
    {
        for(int j=i;j<5;j++)
        {
            for(int k=i;k<=j;k++)
            {
                tmp.a[i][j]=min(tmp.a[i][j],le.a[i][k]+ri.a[k][j]);
            }
        }
    }
    return tmp;
}
char s[N];
int num[N];

void build(int rt,int l,int r)
{
    if(l==r)
    {
        t[rt].init();
        for(int i=0;i<5;i++)t[rt].a[i][i]=0;
        if(num[l]==2){t[rt].a[0][0]=1;t[rt].a[0][1]=0;}
        if(num[l]==0){t[rt].a[1][2]=0;t[rt].a[1][1]=1;}
        if(num[l]==1){t[rt].a[2][2]=1;t[rt].a[2][3]=0;}
        if(num[l]==7){t[rt].a[3][3]=1;t[rt].a[3][4]=0;}
        if(num[l]==6){t[rt].a[3][3]=1;t[rt].a[4][4]=1;}
        return ;
    }
    int m=(l+r)>>1;
    build(rt<<1,l,m);
    build(rt<<1|1,m+1,r);
    t[rt]=Merge(t[rt<<1],t[rt<<1|1]);
}

void  query(int rt,int l,int r,int ql,int qr)
{
    if(l>=ql && r<=qr)
    {
        if(l==ql) ans=t[rt];
        else ans=Merge(ans,t[rt]);
        return ;
    }
    int m=(l+r)>>1;
    if(ql<=m) query(rt<<1,l,m,ql,qr);
    if(qr>m) query(rt<<1|1,m+1,r,ql,qr);
}

int main()
{
    int n,m;
    cin>>n>>m;
    scanf("%s",s+1);
    for(int i=1;i<=n;i++)num[i]=s[i]-'0';
    build(1,1,n);

    while(m--)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        ans.init();
        query(1,1,n,l,r);
        printf("%d\n",ans.a[0][4]==inf?-1:ans.a[0][4]);
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值