题意简述
给定一个序列 a a a,长度 < = 35000 <=35000 <=35000。定义一个区间 [ l , r ] [l,r] [l,r]的得分为这段区间内不同的数的个数。请你将这个序列划分成 k ( < = 50 ) k(<=50) k(<=50)段,使得每段的分数加起来最大。
思路
设
d
p
[
j
]
[
i
]
dp[j][i]
dp[j][i]表示前
i
i
i个分成了
j
j
j段的最大得分和,
d
(
l
,
r
)
d(l,r)
d(l,r)表示
[
l
,
r
]
[l,r]
[l,r]内不同的数个数,那么
(这里不知为啥公式爆了,看图凑合一下),其中
p
p
p枚举前面的一个点(即
1
<
=
p
<
i
1<=p<i
1<=p<i)。
珂是这个转移大概是
O
(
n
2
k
)
O(n^2k)
O(n2k)的,完全不能承受。
观察数据范围,发现时间复杂度大概是 O ( n k ) O(nk) O(nk),或者带个 l o g log log,根号之类的。
不能用状态数优化这个 D P DP DP,那就考虑用数据结构优化。发现 d p [ j ] [ i ] dp[j][i] dp[j][i]只和 d p [ j − 1 ] [ ∗ ] dp[j-1][*] dp[j−1][∗]有关(这就是为什么我把 j j j放到了第一维而不是第二维)。
那么 d p [ i ] dp[i] dp[i]依赖的状态就只有一个序列了。然后我们就是要求这个序列的整体最大值,其中第 p p p个点的权值是 d p [ j − 1 ] [ p ] + d ( p + 1 , j ) dp[j-1][p]+d(p+1,j) dp[j−1][p]+d(p+1,j)。
我们珂以把这个东西想象成这样:初始每个点都是 d p [ j − 1 ] [ p ] dp[j-1][p] dp[j−1][p],然后通过一些修改操作(区间加)变成 d p [ j − 1 ] [ p ] + d ( p + 1 , j ) dp[j-1][p]+d(p+1,j) dp[j−1][p]+d(p+1,j)。
这样考虑,设 p r e [ i ] pre[i] pre[i]表示: a [ i ] a[i] a[i]上一次出现的位置 + 1 +1 +1。(如果没有上一次,那么 p r e [ i ] = 1 pre[i]=1 pre[i]=1)那么,对于一个种类 a [ i ] a[i] a[i],它的贡献就是把 [ p r e [ i ] , i ] [pre[i],i] [pre[i],i]区间 + 1 +1 +1。然后点 d ( p + 1 , j ) d(p+1,j) d(p+1,j)的值就是 [ 1 , p ] [1,p] [1,p]之间加上值的最大值了。
这个维护区间种类数的方式十分常见!!!请各位记好这个trick!!!
(然后解释一下为什么是这样的,如果你能想明白,跳过)
假设
a
a
a序列是这样的:
1 5 2 1 4 5
易得 p r e pre pre数组是这样的:
1 1 1 2 1 3
然后我们考虑第一个位置。区间加
[
p
r
e
[
1
]
,
1
]
[pre[1],1]
[pre[1],1],也就是
[
1
,
1
]
[1,1]
[1,1]。
然后就是区间加
[
1
,
2
]
[1,2]
[1,2]
[
1
,
3
]
[1,3]
[1,3]
[
2
,
4
]
[2,4]
[2,4]
[
1
,
5
]
[1,5]
[1,5]
[
3
,
6
]
[3,6]
[3,6]
加完之后我们发现区间变成了这样:
5 4 3 2 1 1
其中这个序列的第
i
i
i个位置表示
[
i
,
e
n
d
]
[i,end]
[i,end](
e
n
d
end
end是最后一个位置)中有多少不同的种类。
区间加
[
p
r
e
[
1
]
,
1
]
[pre[1],1]
[pre[1],1]的意义是:在
[
p
r
e
[
1
]
,
1
]
[pre[1],1]
[pre[1],1]区间内,每个位置到末尾都能包含
a
[
1
]
a[1]
a[1]
区间加
[
p
r
e
[
2
]
,
2
]
[pre[2],2]
[pre[2],2]的意义是:在
[
p
r
e
[
2
]
,
2
]
[pre[2],2]
[pre[2],2]区间内,每个位置到末尾都能包含
a
[
2
]
a[2]
a[2]
…
区间加
[
p
r
e
[
6
]
,
6
]
[pre[6],6]
[pre[6],6]的意义是:在
[
p
r
e
[
6
]
,
6
]
[pre[6],6]
[pre[6],6]区间内,每个位置到末尾都能包含
a
[
6
]
a[6]
a[6]
也许你会说:那第一个位置到最后一个位置不是也能包含
a
[
6
]
a[6]
a[6]么,但是这一段在算上一个和
a
[
6
]
a[6]
a[6]相同的数(即
a
[
2
]
a[2]
a[2])的时候,已经被加过了。如果再加一次,就重复了。
(也就是说,只加
p
r
e
[
i
]
pre[i]
pre[i]到
i
i
i这一段保证了不会重复,而且也不可能会有遗漏)
(顺便说一句,举一个实例解释问题是很有效的)
然后我们发现,我们珂以拿线段树维护这个东西。对于一个 d p [ j ] [ i ] dp[j][i] dp[j][i],初始值是 d p [ j − 1 ] [ 1 i ] dp[j-1][1~i] dp[j−1][1 i],然后区间加即可,最后 d p [ j ] [ i ] dp[j][i] dp[j][i]的值就是查询区间 [ 1 , j ] [1,j] [1,j]的最大值。
代码:
#include<bits/stdc++.h>
using namespace std;
namespace Flandre_Scarlet
{
#define N 45000
#define MEM(x,a) memset(x,a,sizeof(x))
#define FK(x) MEM(x,0)
#define F(i,l,r) for(int i=l;i<=r;++i)
#define D(i,r,l) for(int i=r;i>=l;--i)
int n,k,a[N];
void R1(int &x)
{
x=0;char c=getchar();int f=1;
while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=(f==1)?x:-x;
}
void Input()
{
R1(n),R1(k);
F(i,1,n) R1(a[i]);
}
int dp[51][N];
class SegmentTree
{
public:
struct node
{
int l,r;
int s,a;
}tree[N<<2];
#define ls (index<<1)
#define rs (index<<1|1)
#define L tree[index].l
#define R tree[index].r
#define S tree[index].s
#define A tree[index].a
#define lL tree[ls].l
#define lR tree[ls].r
#define lS tree[ls].s
#define lA tree[ls].a
#define rL tree[rs].l
#define rR tree[rs].r
#define rS tree[rs].s
#define rA tree[rs].a
void Update(int index)
{
S=max(lS,rS);
}
void Build(int pos,int l,int r,int index)
{
L=l,R=r,S=0,A=0;
if (l==r)
{
S=dp[pos][l-1];
return;
}
int mid=(l+r)>>1;
Build(pos,l,mid,ls);
Build(pos,mid+1,r,rs);
Update(index);
}
void AddOne(int x,int index)
{
S+=x;A+=x;
}
void PushDown(int index)
{
if (A)
{
AddOne(A,ls);
AddOne(A,rs);
A=0;
}
}
void Add(int l,int r,int x,int index)
{
if (l>R or L>r) return;
if (l<=L and R<=r) return AddOne(x,index);
PushDown(index);
Add(l,r,x,ls);
Add(l,r,x,rs);
Update(index);
}
int Query(int l,int r,int index)
{
if (l>R or L>r) return 0;
if (l<=L and R<=r) return S;
PushDown(index);
return max(Query(l,r,ls),Query(l,r,rs));
}
}T;
int pos[N];
int pre[N];
void Soviet()
{
F(i,1,n)
{
pre[i]=pos[a[i]]+1;
pos[a[i]]=i;
}
F(i,1,k)
{
T.Build(i-1,1,n,1);
F(j,1,n)
{
T.Add(pre[j],j,1,1);
dp[i][j]=T.Query(1,j,1);
}
}
printf("%d\n",dp[k][n]);
}
void IsMyWife()
{
Input();
Soviet();
}
}
int main()
{
Flandre_Scarlet::IsMyWife();
return 0;
}