1. 程式人生 > 實用技巧 >CF868F Yet Another Minimization Problem 決策單調性

CF868F Yet Another Minimization Problem 決策單調性

題意:

顯然的 DP 式子 \(f_{i,j}=\min f_{k,j-1}+w(k,i)\)

滾掉第二維可以化簡為 \(g_i=\min f_k+w(k,i)\)

這個式子符合決策單調性,證明:

$\forall a<b<c<d\ w(a,c)+w(b,d)\le w(a,d)+w(b,c)且w(b,c)\le w(a,d) $

由於 \((b,c)\in(a,d)\) 所以 \(w(b,c)\le w(a,d)\) 所以第二個式子顯然成立

那麼我們開始推第一個式子

\(w(b,d)-w(b,c)\le w(a,d)-w(a,c)\)

我們記 \(val(x,y,z,w)\)

表示 \([z,w]\) 中與 \([x,y]\) 中相同的元素對數

那麼原式等價於

\(val(b,c,c,d)\le val(a,c,c,d)\)

由於 \([b,c]\in[a,c]\) 所以 \([a,c]\) 中元素對數不少於 \([b,c]\) 中元素對數所以上式顯然成立

\(QED.\)


好,既然知道了 DP 可以斜率優化,那麼我們可以考慮 二分棧 或者 分治

由於二分棧有一個弊端就是必須能快速計算 \(w(i,j)\) 不然二分時統計答案複雜度過大就會白給,但不巧這個題不能快速計算 \(w(i,j)\) 所以我們只能採取分治寫法

每次劃分決策區間和二分割槽間,找最優決策點的時候只用暴力挪指標,開個桶更新一下相同元素對數,由於每個元素被挪次數不超過 \(\log\)

所以總複雜度是 \(O(n\log )\)

程式碼:

#include<bits/stdc++.h>

using namespace std;

namespace zzc
{
	const int maxn = 1e5+5;
	long long f[25][maxn];
	int a[maxn],num[maxn];
	int n,m,cur,L,R;
	long long ans; 
	
	void calc(int l,int r)
	{
		while(l<L) L--,ans+=num[a[L]],num[a[L]]++;
		while(R<r) R++,ans+=num[a[R]],num[a[R]]++;
		while(l>L) num[a[L]]--,ans-=num[a[L]],L++;
		while(R>r) num[a[R]]--,ans-=num[a[R]],R--;
	}
	
	void solve(int l,int r,int ll,int rr)
	{
		if(l>r) return ;
		int mid=(l+r)>>1,p;
		for(int i=ll;i<=min(mid,rr);i++)
		{
			calc(i+1,mid);
			if(f[cur-1][i]+ans<f[cur][mid])
			{
				f[cur][mid]=f[cur-1][i]+ans;
				p=i;
			}
		}
		solve(l,mid-1,ll,p);
		solve(mid+1,r,p,rr);
	}
	
	void work()
	{
		memset(f,0x7f,sizeof(f));
		scanf("%d%d",&n,&m);L=1;
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
		for(int i=1;i<=n;i++) calc(1,i),f[1][i]=ans;
		for(int i=2;i<=m;i++) cur=i,solve(1,n,1,n);
		printf("%lld\n",f[m][n]);
	}

}

int main()
{
	zzc::work();
	return 0;
}