1. 程式人生 > 實用技巧 >Alternating Strings Gym - 100712D 簡單dp && Alternating Strings II Gym - 100712L 資料結構優化dp

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的長度,就比如下面的串

0101101(下標從1開始)

i=7的話,如果k無限大,那麼dp[i]就可以由dp[3]、dp[2]、dp[1]得到,因為下標4、5位置都是1,那麼就說明如果對於變數j<4,那麼子串[j+1,i]就可以滿足題目要求

大致意思理解就可以,細節之處可以自己改一下,畢竟每一個人寫的方式不一樣

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(單調佇列維護):