题目大意:
给定 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
很显然可以想到单调队列,但区间权值很难维护,这时要考虑分治。
我们按层 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
#include
#include
#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]);
}