luogu P1415 拆分数列 序列DP
做起来不太难,但是很难想的一道题。
分两个步骤,第一个步骤是先求出,最后一个数字是多少。
我们考虑d[i]表示,[1,i]最后一个数字最小情况下,最后一个数字的开始位置。
那转移方程很显然,d[i] = j(满足s[d[j - 1],j - 1]<s[j,i],且j距离i最近,这样子最小)。
这样我们就求除了最后一个数字是多少。
第二步,我们类似的从后推一遍。
用f[i]表示,[i,n]第一个数字最大情况下,第一个数字的结束位置。
转移方程依旧显然,f[i] = j(满足s[i,j] < s[j + 1,f[j + 1]]且离i最远的j,这样子最大)。
然后我们最后顺着f[i]输出即可。
但是有一些问题,就是我们第一步骤求出的最后一个数字,可能存在前导0。
比如100300这个例子,300作为最后一个数字不应该吞并前导零。
而1234050这个例子,50作为最后一个数字应该吞并前导零。
那这个如何处理呢,我们考虑在进行第二次dp前,让最后一个数字的所有前导零的f值全部为n。然后我们从非零的位置开始DP。可以理解成,我先让所有前导零与最后一个数字合并,如果前面的某个数字x发现合并了后面这些前导零更优的时候,我允许他把它合并掉,即f[x]指向某些前导零。有种有需要,则自取的感觉。
1 #include <cstdio> 2 #include <algorithm> 3 #include <cstring> 4 using namespace std; 5 char s[510]; 6 int d[510],f[510]; 7 int len; 8 bool cmp(int x1,int y1,int x2,int y2) 9 {//若[x2,y2] > [x1,y1]则返回true 10 while (s[x1] == 0 && x1 != y1) 11 x1++; 12 while (s[x2] == 0 && x2 != y2) 13 x2++; 14 if (y2 - x2 > y1 - x1) 15 return true; 16 else if (y2 - x2 < y1 - x1) 17 return false; 18 for (int i = x1;i <= y1;i++) 19 if (s[i] < s[x2 + i - x1]) 20 return true; 21 else if (s[i] > s[x2 + i - x1]) 22 return false; 23 return false; 24 } 25 void print(int x,int y) 26 { 27 for (int i = x;i <= y;i++) 28 printf("%d",s[i]); 29 if (y != len) 30 printf(","); 31 } 32 bool check(int x,int y) 33 { 34 for (int i = x;i <= y;i++) 35 if (s[i] != 0) 36 return false; 37 return true; 38 } 39 int main() 40 { 41 scanf("%s",s + 1); 42 len = strlen(s + 1); 43 for (int i = 1;i <= len;i++) 44 s[i] -= '0'; 45 //d[i]表示,[1,i]最后一个数字最小情况下,最后一个数字的开始位置。 46 d[1] = 1; 47 for (int i = 2;i <= len;i++) 48 for (int j = i;j >= 1;j--) 49 if (cmp(d[j - 1],j - 1,j,i) == true) 50 { 51 d[i] = j; 52 break; 53 } 54 //f[i]表示,[i,n]第一个数字最大情况下,第一个数字的结束位置。 55 int t=d[len]; 56 do 57 { 58 f[t]=len; 59 t--; 60 }while ((t>=1)&&(s[t]==0)); 61 f[d[len]] = len; 62 for (int i = t;i >= 1;i--) 63 for (int j = d[len] - 1;j >= i;j--) 64 if (cmp(i,j,j + 1,f[j + 1]) == true) 65 { 66 f[i] = j; 67 break; 68 } 69 for (int i = 1;i <= len;i++) 70 { 71 print(i,f[i]); 72 i = f[i]; 73 } 74 return 0; 75 }
心之所动 且就随缘去吧