题意简述
给定一个序列aaa,长度<=35000<=35000<=35000。定义一个区间[l,r][l,r][l,r]的得分为这段区间内不同的数的个数。请你将这个序列划分成k(<=50)k(<=50)k(<=50)段,使得每段的分数加起来最大。
思路
设dp[j][i]dp[j][i]dp[j][i]表示前iii个分成了jjj段的最大得分和,d(l,r)d(l,r)d(l,r)表示[l,r][l,r][l,r]内不同的数个数,那么
(这里不知为啥公式爆了,看图凑合一下),其中ppp枚举前面的一个点(即1<=p<i1<=p<i1<=p<i)。
珂是这个转移大概是O(n2k)O(n^2k)O(n2k)的,完全不能承受。
观察数据范围,发现时间复杂度大概是O(nk)O(nk)O(nk),或者带个logloglog,根号之类的。
不能用状态数优化这个DPDPDP,那就考虑用数据结构优化。发现dp[j][i]dp[j][i]dp[j][i]只和dp[j−1][∗]dp[j-1][*]dp[j−1][∗]有关(这就是为什么我把jjj放到了第一维而不是第二维)。
那么dp[i]dp[i]dp[i]依赖的状态就只有一个序列了。然后我们就是要求这个序列的整体最大值,其中第ppp个点的权值是dp[j−1][p]+d(p+1,j)dp[j-1][p]+d(p+1,j)dp[j−1][p]+d(p+1,j)。
我们珂以把这个东西想象成这样:初始每个点都是dp[j−1][p]dp[j-1][p]dp[j−1][p],然后通过一些修改操作(区间加)变成dp[j−1][p]+d(p+1,j)dp[j-1][p]+d(p+1,j)dp[j−1][p]+d(p+1,j)。
这样考虑,设pre[i]pre[i]pre[i]表示:a[i]a[i]a[i]上一次出现的位置+1+1+1。(如果没有上一次,那么pre[i]=1pre[i]=1pre[i]=1)那么,对于一个种类a[i]a[i]a[i],它的贡献就是把[pre[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!!!
(然后解释一下为什么是这样的,如果你能想明白,跳过)
假设aaa序列是这样的:
1 5 2 1 4 5
易得preprepre数组是这样的:
1 1 1 2 1 3
然后我们考虑第一个位置。区间加[pre[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
其中这个序列的第iii个位置表示[i,end][i,end][i,end](endendend是最后一个位置)中有多少不同的种类。
区间加[pre[1],1][pre[1],1][pre[1],1]的意义是:在[pre[1],1][pre[1],1][pre[1],1]区间内,每个位置到末尾都能包含a[1]a[1]a[1]
区间加[pre[2],2][pre[2],2][pre[2],2]的意义是:在[pre[2],2][pre[2],2][pre[2],2]区间内,每个位置到末尾都能包含a[2]a[2]a[2]
…
区间加[pre[6],6][pre[6],6][pre[6],6]的意义是:在[pre[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])的时候,已经被加过了。如果再加一次,就重复了。
(也就是说,只加pre[i]pre[i]pre[i]到iii这一段保证了不会重复,而且也不可能会有遗漏)
(顺便说一句,举一个实例解释问题是很有效的)
然后我们发现,我们珂以拿线段树维护这个东西。对于一个dp[j][i]dp[j][i]dp[j][i],初始值是dp[j−1][1 i]dp[j-1][1~i]dp[j−1][1 i],然后区间加即可,最后dp[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;
}
博客介绍了如何解决Codeforces 833B问题,通过维护区间不同数种类数的技巧求解,利用线段树优化状态转移,达到O(nk)的时间复杂度,最大化序列划分的得分。文章强调了这个区间维护方法的重要性,并给出了思路和解释。
539

被折叠的 条评论
为什么被折叠?



