2019 ICPC 南昌网络赛 C题 Hello 2019【线段树维护矩阵+dp】


传送门:C.Hello 2019



A digital string is “good”: when it contains a subsequence 9102 and does not contain a subsequence 8102.

The bad value of a string is defined as how many characters are to remove at least, so that the string satisfies the “good” property.
Output -1 if the string cannot satisfy the “good” property by removing some characters (0 or maybe more).

Input
The first line contains two integers n,Q(1≤n,Q≤2∗105). Where n is the length of the string and Q is the number of queries.

The second line contains a string s that consists entirely of decimal numbers.

The next Q line, each line contains two integers l,r(1≤l≤r≤n), denoting a query.

Output
For each query, output an answer which is the bad value of the substring slsl+1⋯sr from ss.

样例输入
8 3
88988102
1 8
2 8
1 7
样例输出
4
3
-1



这个题目和 Codeforces750E 这题一样,只是换了个数字 = - =


解题思路:

f[i][j]:表示状态 i -> j 的最小花费
 0:什么都没有出现
 1:出现2
 2:出现20
 3:出现201
 4:出现2019
 出现了220的时候,出现8不必管他
 但是如果出现了2012019后,在出现8我们就要把他删除
	 if(x==2)//出现2
     {
       f[0][0]=1;//从什么都没->什么都没,就要把出现的2删去
       f[0][1]=0;//从什么都没->出现2,不需要操作
       //f[1][1]=0;已经出现2或,再出现不需要操作,下同
     }
     else if(x==0)//出现0
     {
       f[1][1]=1;//从出现2->出现2,要把现在的0删去
       f[1][2]=0;//从 2 -> 20,不需与操作,加入即可
     }
     else if(x==1)//出现1
     {
       f[2][2]=1;
       f[2][3]=0;
     }
     else if(x==9)//出现9
     {
       f[3][3]=1;
       f[3][4]=0;
     }
     else if(x==8)//出现8
     {
       f[3][3]=1;//出现201->出现8,删8
       f[4][4]=1;//出现2019->出现8,删8
     }
     
 线段树加一个合并操作;
 那个合并的操作类似于求最短路;
 最后输出f[0][4];表示从什么都没有然后出现"2019"的最小操作数


AC代码:

 #include<iostream>
 #include<algorithm>
 #include<cstring>
 #include<cstdio>
 #include<cmath>
 #include<string.h>
 #define ll long long
 #define inf 0x3f3f3f3f
 using namespace std;
 const int N=1100000;
 int n,q,l,r;
 int a[N];
 char s[N];
 /*
 0:什么都没有出现
 1:出现2
 2:出现20
 3:出现201
 4:出现2019
  */
 struct Node
 {
   int f[5][5];
   int l,r;
   void init(int x)
   {
     memset(f,inf,sizeof(f));
     for(int i=0;i<=4;i++)
       f[i][i]=0;
     if(x==-1)//初始化
     {
       for(int i=0;i<=4;i++)
         f[i][i]=inf;
     }
     if(x==2)
     {
       f[0][0]=1;//从什么都没->什么都没,就要把出现的2删去
       f[0][1]=0;//从什么都没->出现2,不需要操作
       //f[1][1]=0;
     }
     else if(x==0)
     {
       f[1][1]=1;
       f[1][2]=0;
     }
     else if(x==1)
     {
       f[2][2]=1;
       f[2][3]=0;
     }
     else if(x==9)
     {
       f[3][3]=1;
       f[3][4]=0;
     }
     else if(x==8)
     {
       f[3][3]=1;//出现201->出现8,删8
       f[4][4]=1;//出现2019->出现8,删8
     }
   }
 }tree[4*N];
 Node ans;
 Node multi(Node a,Node b)
 {
   Node temp;
   temp.init(-1);
   //temp.l=a.l;temp.r=b.r;
   for(int i=0;i<=4;i++)
   {
     for(int j=0;j<=4;j++)
     {
       for(int k=0;k<=4;k++)
       {
         temp.f[i][j]=min(temp.f[i][j],a.f[i][k]+b.f[k][j]);
       }
     }
   }
   return temp;
 }
 void build(int k,int l,int r)
 {
   tree[k].l=l;tree[k].r=r;
   if(tree[k].l==tree[k].r)
   {
     tree[k].init(a[l]);
     return;
   }
   int mid=(l+r)/2;
   build(2*k,l,mid);
   build(2*k+1,mid+1,r);
   tree[k]=multi(tree[2*k],tree[2*k+1]);
   tree[k].l=l;tree[k].r=r;
 }
 Node quary(int k,int l,int r)
 {
   if(tree[k].l>=l&&tree[k].r<=r)
   {
     return tree[k];
   }
   int mid=(tree[k].l+tree[k].r)/2;
   if(r<=mid)//目标区间在当前区间的右边
     return quary(2*k,l,r);
   if(l>mid)//目标区间在当前区间的左边
     return quary(2*k+1,l,r);
   return multi(quary(2*k,l,mid),quary(2*k+1,mid+1,r));//目标区间在当前区间的中间
 }
 int main()
 {
   scanf("%d%d",&n,&q);
   scanf("%s",s+1);
   for(int i=1;i<=n;i++)//反转字符
     a[i]=s[n-i+1]-'0';
   build(1,1,n);
   while(q--)
   {
     scanf("%d%d",&l,&r);
     int tmp=l;
     l=n-r+1;//反转后,区间端点也要变
     r=n-tmp+1;
     //printf("%d %d\n",l,r);
     ans=quary(1,l,r);
     if(ans.f[0][4]==inf) printf("-1\n");
     else printf("%d\n",ans.f[0][4]);
   }
   return 0;
 }

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值