题意
你有一个有序的数组,现在要插入一个新的数,相信你一定学过二分查找,也知道最坏情况下需要比较几次才能找到新的数该插入什么位置,但是现在,我们稍微改变下套路,把新的数与数组中的每一个数比较都会有一个特定的代价,代价在1-9之间,求最坏情况下,假设你采用最优的比较策略,你会花费多少费用插入新的数。
数据范围
有 t ( t ≤ 10 ) t(t\le 10) t(t≤10)组数据,数组大小 n ≤ 100000 n\le 100000 n≤100000
解法
首先考虑一个常规的区间dp:
f
[
l
]
[
r
]
表
示
查
询
[
l
,
r
]
中
任
意
一
个
数
的
最
大
代
价
f[l][r]表示查询[l,r]中任意一个数的最大代价
f[l][r]表示查询[l,r]中任意一个数的最大代价
转移:
f
[
l
]
[
r
]
=
m
i
n
k
=
l
r
(
m
a
x
(
f
[
l
]
[
k
−
1
]
,
f
[
k
+
1
]
[
r
]
)
+
a
[
k
]
)
f[l][r]=min_{k=l}^r(max(f[l][k-1],f[k+1][r])+a[k])
f[l][r]=mink=lr(max(f[l][k−1],f[k+1][r])+a[k])
这样做是
O
(
n
3
)
O(n^3)
O(n3)的,一般来见似乎没有什么办法优化,但是我们发现最终答案非常的小,所以考虑换一种dp状态:
f
[
l
]
[
k
]
表
示
以
l
为
左
节
点
,
代
价
为
k
时
最
远
的
右
端
点
f[l][k]表示以l为左节点,代价为k时最远的右端点
f[l][k]表示以l为左节点,代价为k时最远的右端点
转移就也是把两个区间拼起来
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
inline int read(){
char c=getchar();int t=0,f=1;
while((!isdigit(c))&&(c!=EOF)){if(c=='-')f=-1;c=getchar();}
while((isdigit(c))&&(c!=EOF)){t=(t<<3)+(t<<1)+(c^48);c=getchar();}
return t*f;
}
int t,n,a[maxn],at[maxn][10],las[maxn],f[maxn][200];
char s[maxn];
signed main(){
t=read();
for(int x=1;x<=t;x++){
scanf("%s",s+1);n=strlen(s+1);
memset(f,0,sizeof(f));memset(las,0,sizeof(las));
for(int i=1;i<=n;i++){
a[i]=s[i]-'0';
}
for(int i=1;i<=n;i++){
las[a[i]]=i;
for(int j=1;j<=9;j++)at[i][j]=las[j];//at是序列自动机
}
for(int i=1;i<=n+1;i++){
for(int j=0;j<=180;j++){
f[i][j]=i-1;
}
}
for(int i=n;i>=1;i--){//倒着转移
for(int j=1;j<=180;j++){
f[i][j]=f[i][j-1];
for(int d=1;d<=min(j,9);d++){
int p=at[f[i][j-d]+1][d];
if(p<i)continue;
f[i][j]=max(f[i][j],f[p+1][j-d]);
}
}
}
for(int i=0;i<=180;i++)
if(f[1][i]==n){printf("Case #%d: %d\n",x,i);break;}
}
return 0;
}