Alternating Strings Gym - 100712D 简单dp && Alternating Strings II Gym - 100712L 数据结构优化dp

比赛链接:https://vjudge.net/contest/405905#problem/D

 

题意:

给你一个长度为n的由0或1构成的串s,你需要切割这个串,要求切割之后的每一个子串长度要小于等于k。且每一个子串内不能全都是01交替,就比如

00101010、11010101这样没有问题,不需要切割,因为前面有两个相同的

01010101、10101010这样就不行,就必须切割开

 

问你最少需要切割多少次

 

题解:

我们设定输入的01串下标从1开始

我们使用dp[i]表示:s字符串的从第1个字符到第i个字符最少需要切割多少次

dp转移方程dp[i+j]=min(dp[i+j],dp[i-1]+1)

可以说我们使用dp[i-1]的值去维护后面dp的值,要保证[i,i+j]这一个子串不需要切割,且长度小于等于k

 

复杂度也就是O(n*k),对于第一题是没问题的

AC代码:

 1 #include <map>
 2 #include <set>
 3 #include <list>
 4 #include <queue>
 5 #include <deque>
 6 #include <cmath>
 7 #include <stack>
 8 #include <vector>
 9 #include <bitset>
10 #include <cstdio>
11 #include <string>
12 #include <cstdlib>
13 #include <cstring>
14 #include <iostream>
15 #include <algorithm>
16 using namespace std;
17 typedef long long ll;
18 typedef unsigned long long ull;
19 const ll mod = 1e9 + 7;
20 const int maxn = 1e3 + 10;
21 const int INF = 0x3f3f3f3f;
22 char s[maxn];
23 int dp[maxn];
24 int main()
25 {
26     int t;
27     scanf("%d", &t);
28     while (t--)
29     {
30         int n, k;
31         memset(dp, INF, sizeof(dp));
32         dp[0] = 0;
33         scanf("%d%d", &n, &k);
34         scanf("%s", s + 1);
35         for (int i = 1; i <= n; ++i)
36         {
37             int last = 0, flag = 0, now = s[i] - '0', nnn = 0;
38             while (1)
39             {
40                 if (flag == 0 || nnn == 1)
41                 {
42                     dp[i + last] = min(dp[i - 1] + 1, dp[i + last]);
43                 }
44                 last++;
45                 if (last == k || i + last > n)
46                     break;
47                 if (nnn)
48                     continue;
49                 if (s[i + last] - '0' == now)
50                 {
51                     nnn = 1;
52                 }
53                 else
54                 {
55                     flag = 1;
56                     now = 1 - now;
57                 }
58             }
59             //if(i==1) printf("%d***\n",last);
60         }
61         printf("%d\n", dp[n] - 1);
62     }
63     return 0;
64 }
65 /*
66 4
67 6 3
68 111000
69 5 2
70 11010
71 3 3
72 110
73 3 3
74 101
75 
76 1
77 4 4
78 1110
79 */
View Code

 

 

题解2(线段树+dp):

但是我上面的dp方程无法优化,因为你要使用dp[i-1]的值去更新dp[i+j]的值,那么最坏情况也就是对于j(1<=j<=k),dp[i-1]可以更新每一个dp[i+j]

那么最坏复杂度就是O(n*k)是无法优化的,所以要另辟蹊径

 

那我们可以把dp转移方程改成

dp[i]=min(dp[i-j]+1,dp[i])

可以说反转了一下,我们需要保证下标为[i-j+1,i]的子串不需要分割且长度小于等于k

那么我们可以使用线段树来维护所有dp[i]的值

对于dp[i]我们可以在线段树中查找区间[i-k,i-1]中的最小值就可以,但是因为题目要求分割后的子串中不能全部是01交替

所以我们查找区间[i-k,i-1]的最小值,我们需要找到以i为结尾,向左边找交替出现01的长度pre,就比如下面的串

0101101(下标从1开始)

i=7的话,如果k无限大,那么dp[i]就可以由dp[3]、dp[2]、dp[1]得到,因为下标4、5位置都是1,那么就说明如果对于变量j<4,那么子串[j+1,i]就可以满足题目要求

然后使用线段树找出来区间[i-k,i-pre]中的最小值就可以了

 

 

大致意思理解就可以,细节之处可以自己改一下,毕竟每一个人写的方式不一样

AC代码:

  1 #include <map>
  2 #include <set>
  3 #include <list>
  4 #include <queue>
  5 #include <deque>
  6 #include <cmath>
  7 #include <stack>
  8 #include <vector>
  9 #include <bitset>
 10 #include <cstdio>
 11 #include <string>
 12 #include <cstdlib>
 13 #include <cstring>
 14 #include <iostream>
 15 #include <algorithm>
 16 #define lson rt<<1,L,mid
 17 #define rson rt<<1|1,mid+1,R
 18 #define mem(a,b) memset(a,b,sizeof(a))
 19 using namespace std;
 20 typedef long long ll;
 21 typedef unsigned long long ull;
 22 const ll mod = 1e9 + 7;
 23 const int maxn = 1e5 + 10;
 24 const int INF = 0x3f3f3f3f;
 25 int root[maxn<<2],dp[maxn];
 26 char s[maxn];
 27 void push_up(int rt)
 28 {
 29     root[rt]=min(root[rt<<1],root[rt<<1|1]);
 30 }
 31 void update(int rt,int L,int R,int pos,int val)
 32 {
 33     if(L==R)
 34     {
 35         root[rt]=val;
 36         return;
 37     }
 38     int mid=(L+R)>>1;
 39     if(pos<=mid)
 40         update(lson,pos,val);
 41     else update(rson,pos,val);
 42     push_up(rt);
 43 }
 44 int query(int rt,int L,int R,int LL,int RR)
 45 {
 46     if(LL<=L && RR>=R)
 47     {
 48         return root[rt];
 49     }
 50     int mid=(L+R)>>1,ans=INF;
 51     if(LL<=mid) ans=min(ans,query(lson,LL,RR));
 52     if(RR>mid) ans=min(ans,query(rson,LL,RR));
 53     return ans;
 54 }
 55 int main()
 56 {
 57     int t;
 58     //update(1,1,2,1,0);
 59     //printf("%d\n",query(1,1,2,1,1));
 60     scanf("%d",&t);
 61     while(t--)
 62     {
 63         int n,k;
 64         //mem(root,INF);
 65         memset(root,INF,sizeof(root));
 66         scanf("%d%d",&n,&k);
 67         update(1,1,n+1,1,0);
 68         update(1,1,n+1,2,1);
 69         dp[1]=0;
 70         dp[2]=1;
 71         //printf("%d*****\n",query(1,1,n+1,2,2));
 72         scanf("%s",s+2);
 73 
 74         int pre=1;
 75         for(int i=3;i<=n+1;++i)
 76         {
 77             if(s[i]==s[i-1])
 78             {
 79                 pre=1;
 80                 dp[i]=query(1,1,n+1,max(i-k,1),i-1)+1;
 81                 //printf("%d ",dp[i]);
 82             }
 83             else
 84             {
 85                 pre++;
 86                 if(pre>=k || pre==i-1)
 87                 {
 88                     dp[i]=query(1,1,n+1,i-1,i-1)+1;
 89                     //printf("%d* ",dp[i]);
 90                 }
 91                 else
 92                 {
 93                     dp[i]=query(1,1,n+1,max(i-k,1),i-pre-1)+1;
 94                     //printf("%d*** ",dp[i]);
 95                 }
 96             }//printf("**\n+1");
 97             update(1,1,n+1,i,dp[i]);
 98             //
 99         }
100         //printf("***\n");
101         printf("%d\n",dp[n+1]-1);
102     }
103     return 0;
104 }
View Code

 

 

题解3(双端队列维护):

我们维护一个单调递增的队列,对于一个位置i,我们需要找到以i为结尾,向左边找交替出现01的长度pre

然后如果pre>k或者pre==i(pre==i表示串从头到i位置都是01交替)

那么dp[i]=dp[i-1]+1;

否则就从队列头部取出元素+1就是dp[i]的值

 

给你四个变量i、j、kk、l(i<j<kk<l,l-i<=k)

现在我们求dp[l]的值,如果dp[j]可以用来维护dp[l](意味这子串[j+1,i]可以当成一个切割后的子串)。如果dp[kk]<dp[j]导致dp[j]被移出队列

那么也就意味着dp[j]无法维护dp[l]的值,这可以吗?

其实是可以的,因为如果dp[j]可以维护dp[l]那么dp[i]也是可以

且如果dp[i]在队列中,dp[i]<dp[j],那么dp[j]丢失了也就没有关系了

 

代码:

 

#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <map>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
const int INF=0x3f3f3f3f;
char s[maxn];
int dp[maxn],que[maxn];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n,k,start=0,last=0;
        //memset(dp,INF,sizeof(dp));
        scanf("%d%d",&n,&k);
        scanf("%s",s+1);
        dp[0]=0;
        que[++last]=0;
        //que[++last]=1;
        int pre=1;
        for(int i=1; i<=n; ++i)
        {
            if(start<last && i-que[start+1]>k)
            {
                //printf("%d %d***\n",que[start+1],i);
                start++;
            }
            if(i==1 || s[i]==s[i-1])
            {
                pre=1;
                dp[i]=dp[que[start+1]]+1;
                //if(i==3) printf("%d %d\n",que[start+1],start);
            }
            else
            {
                pre++;

                if(pre==i || pre>=i-que[start+1])
                {
                    dp[i]=dp[que[last]]+1;
                    //if(i==n)
                }
                else
                {
                    dp[i]=dp[que[start+1]]+1;
                }
            }
            while(start<last && dp[i]<dp[que[last]])
                last--;
            que[++last]=i;
            //printf("%d ",dp[i]);
        }
        //printf("\n");
        printf("%d\n",dp[n]-1);
    }
    return 0;
}

/*
4
6 3
111000
5 2
11010
3 3
110
3 3
101

*/

 

posted @ 2020-11-14 11:02  kongbursi  阅读(56)  评论(0编辑  收藏  举报