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 <stringView Code> 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 */
題解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(單調佇列維護):