CF868F Yet Another Minimization Problem dp+分治

题目大意:
给定 n n n个数,把他分成 k k k段,每段的权值是相同数的对数(指有多少对相同的数),求最小权值和。
n < = 1 0 5 , k < = 20 n<=10^5,k<=20 n<=105k<=20

分析:
显然对于前面的两个状态 j , k j,k j,k j < k jj<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 
#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]);
} 

你可能感兴趣的:(DP)