CF868F 分治优化dp

链接:https://codeforces.com/problemset/problem/868/F

首先n^2m的dp很容易想,f(j,i)表示考虑划分了j个集合,最后一个集合结尾在i的最小值。显然它具有决策单调性,即如果f(j,i)最后一个划分的集合在最优方案下起始位置是k,那么当i增加k一定单调不降(如果有多个最优决策位置选任意一个都是满足的,因为实际上i增大一定是在最右边的最优决策对它影响最小,所以最右的及它左边的都满足该性质),考虑用这个性质优化dp,假设当前j已经确定,要处理所有i在[l,r]内的答案,那么可以先求mid的答案,递归处理左右区间,可以发现左右区间因为之前那个性质可能是决策位置的总长度才等于当前区间的可能长度,所以如果能做到每一层求mid答案与可能决策区间长度同阶即可做到nm^log,而发现不与当前决策区间同阶的限制就是要算mid~可能决策区间右端点的数的贡献,而这个东西发现我们每次分治的时候只要不把整一层信息清空带着当前层的右端点mid到下一层移动右端点移动也是总和n^log了。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+100;
const ll inf=1e18;

template<class T>
void rd(T &x)
{
	char c=getchar();x=0;bool f=0;
	while(!isdigit(c))f|=(c=='-'),c=getchar();
	while(isdigit(c))x=x*10+c-48,c=getchar();
	if(f)x=-x;
}

int n,m,a[N],dep,nw_l,nw_r;
vector<int>bin;
ll cnt[N],f[22][N],res;

ll get(int x)
{return max(0LL,cnt[x]*(cnt[x]-1)/2);}
void add(int x)
{res-=get(a[x]),cnt[a[x]]++,res+=get(a[x]);}
void del(int x)
{res-=get(a[x]),cnt[a[x]]--,res+=get(a[x]);}

void sol(int l,int r,int may_l,int may_r)
{
	ll mn=inf;
	int ps,mid=(l+r)>>1;
	while(nw_r<mid)add(++nw_r);;
	while(nw_r>mid)del(nw_r--);
	for(int i=min(may_r,mid);i>=may_l;i--)
	{
		while(nw_l>i)add(--nw_l);
		while(nw_l<i)del(nw_l++);
		if(res+f[dep-1][i-1]<=mn)ps=i,mn=res+f[dep-1][i-1];
	}
	f[dep][mid]=mn;
	if(l<mid)sol(l,mid-1,may_l,ps);
	if(r>mid)sol(mid+1,r,ps,may_r);

}

int main()
{
	rd(n),rd(m);
	for(int i=1;i<=n;i++)
		rd(a[i]);
	for(int i=1;i<=n;i++)
		add(i),f[1][i]=res;
	memset(cnt,0,sizeof cnt);
	for(int j=2;j<=m;j++)
	{
		memset(cnt,0,sizeof cnt);
		nw_l=1,nw_r=0,dep=j,res=0;
		sol(1,n,1,n);
	}
	printf("%lld\n",f[m][n]);
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值