【Codeforces 868F】Yet Another Minimization Problem
动态规划,决策单调性,分治
题目大意:
给定一个数列,要将其分成恰好 $k$ 段,每段的花费为其中相同元素的对数。求最小总花费。
题解:
考虑 DP。
令 $f[i][j]$ 表示前 $i$ 个数划分为 $j$ 段的最小花费。
$f[i][j]=\min\{f[k][j-1]+w(k+1,i)\}$,其中 $w(x,y)$ 表示 $x\sim y$ 中相同元素的对数。
转移是 $O(n)$ 的,时间复杂度 $O(n^2k)$。
这个转移满足决策单调性,即若 $f[i]$ 由 $x$ 转移,$f[i+1]$ 由 $y$ 转移,那么 $x\leq y$。
证明:
假设 $x\lt y$ 且存在 $i \gt y$ 满足 $f[y][k]+w(y+1,i)\leq f[x][k]+w(x+1,i)$,$f[x][k]+w(x+1,i+1)< f[y][k]+w(y+1,i+1)$,
那么可推出 $w(x+1,i+1)-w(x+1,i)<w(y+1,i+1)-w(y+1,i)$。
其中 $w(l,r+1)-w(l,r)$ 相当于在 $a_{l\sim r}$ 中加入 $a_{r+1}$ 后的变化量。
由于 $[x+1,i]$ 的长度大于 $[y+1,i]$,因此同时插入 $a_{i+1}$ 时,前者的变化量一定不少于后者,与上面的式子矛盾。
然后我们可以分治计算 DP 值。
假设我们当前要计算 $[l,r]$ 这些位置的 DP 值,已知决策点在 $[L,R]$ 内。
我们先取 $mid=\lfloor\frac{l+r}2\rfloor$,然后求出 $mid$ 这个位置的决策点 $p$。
那么 $[l,mid-1]$ 这些位置的决策点在 $[L,p]$ 内,$[mid+1,r]$ 这些位置的决策点在 $[p,R]$ 内。递归计算即可。
考虑计算的时候 $w(l,r)$ 的值如何计算。
由于每个区间的 $mid$ 相同,即右端点相同,所以计算该区间时,每次只会增加/减少一个数。暴力计算即可。
也可以像莫队那样从之前的状态转移过来,复杂度均摊正确。
时间复杂度 $O(nk\log n)$。
Code:
#include<iostream>
#include<cstring>
using namespace std;
typedef long long LL;
const int N=1e5+6;
int n,k,a[N];LL dp[2][N];
namespace W{
int l,r,t[N];
LL s;
LL work(int L,int R){
while(r<R)s+=t[a[++r]]++;
while(l>L)s+=t[a[--l]]++;
while(l<L)s-=--t[a[l++]];
while(r>R)s-=--t[a[r--]];
return s;
}
}
void solve(int l,int r,int L,int R,LL*f,LL*g){
if(L>R||l>r)return;
const int mid=l+r>>1;
int p=0;
for(int i=L;i<=R&&i<mid;++i){
const LL w=W::work(i+1,mid);
if(f[mid]>w+g[i])f[mid]=w+g[i],p=i;
}
solve(l,mid-1,L,p,f,g),solve(mid+1,r,p,R,f,g);
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;++i)cin>>a[i];
W::l=1,W::r=0;
memset(*dp,0x3f,(n+2)<<3);
**dp=0;
for(int i=1;i<=k;++i)memset(dp[i&1],0x3f,(n+2)<<3),solve(1,n,0,n-1,dp[i&1],dp[i&1^1]);
cout<<dp[k&1][n]<<'\n';
return 0;
}