【洛谷P1415】拆分数列【dp】
题目大意:
题目链接:https://www.luogu.org/problemnew/show/P1415
给出一列数字,需要你添加任意多个逗号将其拆成若干个严格递增的数。如果有多组解,则输出使得最后一个数最小的同时,字典序最大的解(即先要满足最后一个数最小;如果有多组解,则使得第一个数尽量大;如果仍有多组解,则使得第二个数尽量大,依次类推……)。
思路:
由于要先把最后一位尽量小,考虑先求出最后一位的最小答案。
设表示在满足前位的划分单调递增时,以第位为末,且这一次的划分的值尽量小的时候划分的首位。
简单来说,就是找到一个满足在开始划分可以满足单调递增且尽量小的方案数。
显然只需要让尽量大就可以了。就倒序枚举,如果就让
其中 可以预处理搞定。
接下来求出满足前面尽量大的答案。
设表示在满足后位的划分满足单调递增时,以第位为首,且这一次的划分的值尽量大的时候划分的末位。
显然初始化
推方程思路和基本一样。
注意处理前导0。可以用表示的前导0个数。
这样的时间复杂度就是的。实际上跑起来会小得多。
代码:
#include <cstdio>
#include <string>
#include <iostream>
using namespace std;
const int N=510;
int n,a[N],f[N],g[N],sum[N][N];
string t[N][N];
bool check(int S1,int T1,int S2,int T2)
{
if (T1-S1-sum[S1][T1]<T2-S2-sum[S2][T2]) return 1;
if (T1-S1-sum[S1][T1]>T2-S2-sum[S2][T2]) return 0;
string s1=t[S1+sum[S1][T1]][T1],s2=t[S2+sum[S2][T2]][T2];
for (int i=0;i<min(s1.size(),s2.size());i++)
{
if (s1[i]<s2[i]) return 1;
if (s1[i]>s2[i]) return 0;
}
return 0;
}
void dp1()
{
f[1]=1;
for (int i=2;i<=n;i++)
for (int j=i;j>=1;j--)
if (check(f[j-1],j-1,j,i))
{
f[i]=j;
break;
}
}
void dp2()
{
g[f[n]]=n;
int k=0;
for (int i=f[n]-1;!a[i]&&i;i--) g[i]=n,k++;
for (int i=f[n]-1-k;i>=1;i--)
for (int j=f[n]-1;j>=i;j--)
if (check(i,j,j+1,g[j+1]))
{
g[i]=j;
break;
}
int i;
}
int main()
{
while (scanf("%1d",&a[++n])==1)
for (int i=1;i<=n;i++)
{
t[i][n]=t[i][n-1]+(char)(a[n]+48);
if (sum[i][n-1]==max(n-i,0)&&!a[n]) sum[i][n]=sum[i][n-1]+1;
else sum[i][n]=sum[i][n-1];
}
n--;
dp1();
dp2();
int j=g[1];
for (int i=1;i<=n;i++)
{
printf("%d",a[i]);
if (i==j&&i!=n)
{
j=g[i+1];
putchar(',');
}
}
return 0;
}
这道题思路还是不难,但是敲了我。。。恐怖如斯
话说这道题有要求的加强版 P2282[HNOI2003]历史年份,好像是用二分判断两个子串的大小,线段树维护f的最大值。。。orz