题目大意:
题目链接:https://www.luogu.org/problemnew/show/P1415
给出一列数字,需要你添加任意多个逗号将其拆成若干个严格递增的数。如果有多组解,则输出使得最后一个数最小的同时,字典序最大的解(即先要满足最后一个数最小;如果有多组解,则使得第一个数尽量大;如果仍有多组解,则使得第二个数尽量大,依次类推……)。
思路:
由于要先把最后一位尽量小,考虑先求出最后一位的最小答案。
设
f
[
i
]
f[i]
f[i]表示在满足前
i
i
i位的划分单调递增时,以第
i
i
i位为末,且这一次的划分的值尽量小的时候划分的首位。
简单来说,就是找到一个
j
j
j满足在
j
j
j开始划分可以满足单调递增且
j
∼
i
‾
\overline{j\sim i}
j∼i尽量小的方案数。
显然只需要让
j
j
j尽量大就可以了。就倒序枚举
j
j
j,如果
f
[
j
−
1
]
∼
j
−
1
‾
<
j
∼
i
‾
\overline{f[j-1]\sim j-1}<\overline{j\sim i}
f[j−1]∼j−1<j∼i就让
f
[
i
]
=
j
f[i]=j
f[i]=j
其中
x
∼
y
‾
\overline{x\sim y}
x∼y 可以预处理搞定。
接下来求出满足前面尽量大的答案。
设
g
[
i
]
g[i]
g[i]表示在满足后
i
i
i位的划分满足单调递增时,以第
i
i
i位为首,且这一次的划分的值尽量大的时候划分的末位。
显然初始化
d
[
f
[
n
]
]
=
n
d[f[n]]=n
d[f[n]]=n
推方程思路和
f
f
f基本一样。
注意处理前导0。可以用
s
u
m
[
x
]
[
y
]
sum[x][y]
sum[x][y]表示
x
∼
y
‾
\overline{x\sim y}
x∼y的前导0个数。
这样的时间复杂度就是
O
(
n
3
)
O(n^3)
O(n3)的。实际上跑起来会小得多。
代码:
#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;
}
这道题思路还是不难,但是敲了我
3
h
3h
3h。。。恐怖如斯
话说这道题有要求
O
(
n
log
n
)
O(n\log n)
O(nlogn)的加强版
→
\to
→ P2282[HNOI2003]历史年份,好像是用
h
a
s
h
+
hash+
hash+二分判断两个子串的大小,线段树维护f的最大值。。。orz