https://www.luogu.org/problemnew/show/P1415
DP
数列长度过大无法枚举,考虑DP
设f1[i]储存以第i个字符为结尾时,的最后一个数最小时,这个数的开头的位置
(很难想有木有)
OK,状态有了,方程想一想就出来了:
设num[i][j]为数列中从i到j的数连起来后的值,len为数列长度
则
如果num[ j ][ i ]>num[ f1[ j-1 ] ][ j-1 ]( j从i到1 ,1<=i<=len)则f1[ i ]等于j,并且直接退出当前循环
因为j从后往前枚举,所以一旦找到符合的j就是num[ f1[ i ] ][ i ]的最小值
至于num的判断只要打个暴力循环就好了..数据太小..
然后是输出字典序最小的答案(划重点)
仍然是DP..
设f2[i]存储以第i个字符为开头时,的最后一个数最小时,这个数的结尾的位置(貌似只是倒过来了..)
同样如果num[i][j]<num[f2[j+1]][j+1]( j从n到i ,i从n到1)则f2[i]等于j,并且直接退出当前循环.
还有f2[f[len]]初始为len
有一点要注意最后一个数可以有前导0..
"如果第一次dp计算出最小末尾为50,但输入是……00050。
这样上面的转移方程不会将000和50分成一组,因为I≤j≤f[n] 。
这样000所在状态就和状态定义不符,它没表示出最大末尾。"
__by Rapiz
所以要先把最后一个数的前导0单独处理
听同机房的dalao说好像数据可以扩10倍,判断的话就要用神奇的后缀数组什么的..其实我也不清楚唉
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> using namespace std; char s[507]; int n,f1[507],f2[507]; inline bool pd(int la,int ra,int lb,int rb)//暴力的判断函数 { while(s[la]==0&&la<ra) la++; while(s[lb]==0&&lb<rb) lb++; //cout<<la<<" "<<ra<<" "<<lb<<" "<<rb<<endl; if(ra-la!=rb-lb) { if(ra-la>rb-lb) return 1; else return 0; } int len=ra-la; //cout<<len<<endl; //printf("%d %d",s[la+0],s[lb+0]); //cout<<endl; for(int i=0;i<=len;i++) if(s[la+i]!=s[lb+i]) return s[la+i]>s[lb+i]; return 0; } int main() { scanf("%s",s+1); for(int i=1;s[i];i++,n++) s[i]-='0'; //DP开始 for(int i=1;i<=n;i++) { for(int j=i;j>0;j--) if(pd(j,i,f1[j-1],j-1)) { f1[i]=j; break; } } int tot=1; //cout<<f1[n]; for(int i=f1[n]-1;i&&s[i]==0;i--) f2[i]=n,tot++; f2[f1[n]]=n; for(int i=f1[n]-tot;i>0;i--) { f2[i]=i; for(int j=f1[n]-1;j>=i;j--) if(pd(j+1,f2[j+1],i,j)) { f2[i]=max(f2[i],j); break; } } int pos=f2[1]; for(int i=1;i<=n;i++) { if(i==pos&&i!=n) { printf("%d,",s[i]); pos=f2[i+1]; continue; } printf("%d",s[i]); } return 0; }