题目大意:
给定
n
n
n个数,把他分成
k
k
k段,每段的权值是相同数的对数(指有多少对相同的数),求最小权值和。
n
<
=
1
0
5
,
k
<
=
20
n<=10^5,k<=20
n<=105,k<=20
分析:
显然对于前面的两个状态
j
,
k
j,k
j,k且
j
<
k
j<k
j<k,如果在某个状态
k
k
k比
j
j
j优时,那么这个状态后
k
k
k一定也比
j
j
j优。因为往
j
j
j后面加一个数权值的增量一定大于等于
k
k
k的增量。也就是满足决策单调性。
很显然可以想到单调队列,但区间权值很难维护,这时要考虑分治。
我们按层
d
p
dp
dp,我们设
c
a
l
c
(
l
,
r
,
l
l
,
r
r
)
calc(l,r,ll,rr)
calc(l,r,ll,rr)为区间
[
l
,
r
]
[l,r]
[l,r]是由上一行的区间
[
l
l
,
r
r
]
[ll,rr]
[ll,rr]转移而来。我们取
l
,
r
l,r
l,r的
m
i
d
mid
mid,然后暴力枚举
[
l
l
,
r
r
]
[ll,rr]
[ll,rr]的每一个元素,记录最优决策点
p
p
p,然后再分治处理
(
l
,
m
i
d
,
l
l
,
p
)
(l,mid,ll,p)
(l,mid,ll,p)与
(
m
i
d
+
1
,
r
,
p
,
r
r
)
(mid+1,r,p,rr)
(mid+1,r,p,rr)即可。
代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#define LL long long
const int maxn=1e5+7;
using namespace std;
int n,k,l,r;
int a[maxn],num[maxn];
LL ans;
LL f[maxn][21];
void updata(int x,int op)
{
if (op==1)
{
ans+=(LL)num[a[x]];
num[a[x]]++;
}
else
{
num[a[x]]--;
ans-=(LL)num[a[x]];
}
}
LL getsum(int x,int y)
{
while (r<y) updata(r+1,1),r++;
while (r>y) updata(r,-1),r--;
while (l<x) updata(l,-1),l++;
while (l>x) updata(l-1,1),l--;
return ans;
}
void calc(int l,int r,int ll,int rr,int d)
{
int mid=(l+r)/2;
int p;
for (int i=ll;i<=min(mid-1,rr);i++)
{
if (f[i][d-1]+getsum(i+1,mid)<=f[mid][d])
{
p=i;
f[mid][d]=f[i][d-1]+getsum(i+1,mid);
}
}
if (l==r) return;
calc(l,mid,ll,p,d);
calc(mid+1,r,p,rr,d);
}
int main()
{
scanf("%d%d",&n,&k);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
for (int i=0;i<=n;i++)
{
for (int j=0;j<=k;j++) f[i][j]=1e15;
}
l=1,r=0;
f[0][0]=0;
for (int i=1;i<=k;i++) calc(1,n,0,n,i);
printf("%lld\n",f[n][k]);
}