标签:mini upd codeforce span int 次数 clu 还需要 rip
给出一个长度为 \(n\) 的序列,你需要将它分为 \(k\) 段,使得每一段的价值和最小,每一段的价值是这一段内相同的数的个数
题面
容易想到设 \(f[i][j]\) 表示前 \(i\) 个数分成 \(j\) 段的最小代价
\(f[i][j]=min(f[k][j-1]+w(k+1,i))\)
这个\(DP\)有决策单调性,可以分治优化
设 \(solve(l,r,L,R)\) 表示用区间 \([L,R]\) 内的决策去更新 \([l,r]\) 的函数,找到 \(mid\) 的决策点然后分治下去就可以了
对于 \(w\) 函数的处理还需要注意:
我们维护两个全局指针,每一次移动到指定的区间,这样就可以 \(O(1)\) 转移了,均摊指针的移动次数是 \(O(n*log)\) 的
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int n,a[N],m,t[N],p=1,q=0;ll f[N][22],tot=0;
inline void upd(int l,int r){
while(q<r)q++,tot+=t[a[q]],t[a[q]]++;
while(p>l)p--,tot+=t[a[p]],t[a[p]]++;
while(q>r)t[a[q]]--,tot-=t[a[q]],q--;
while(p<l)t[a[p]]--,tot-=t[a[p]],p++;
}
inline void solve(int l,int r,int L,int R,int k){
if(l==r){
for(int i=min(R,r);i>=L;i--)
upd(i+1,l),f[l][k]=min(f[l][k],f[i][k-1]+tot);
return ;
}
int mid=(l+r)>>1,st=0;
for(int i=min(mid-1,R);i>=L;i--){
upd(i+1,mid);
if(tot+f[i][k-1]<f[mid][k])f[mid][k]=tot+f[i][k-1],st=i;
}
if(st)solve(l,mid,L,st,k),solve(mid+1,r,st,R,k);
else solve(mid+1,r,L,R,k);
}
int main(){
freopen("pp.in","r",stdin);
freopen("pp.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
memset(f,127/3,sizeof(f));f[0][1]=0;
for(int i=1;i<=n;i++)f[i][1]=f[i-1][1]+t[a[i]],t[a[i]]++;
memset(t,0,sizeof(t));
for(int i=2;i<=m;i++)
solve(1,n,1,n,i);
cout<<f[n][m]<<endl;
return 0;
}
Codeforces 868F. Yet Another Minimization Problem
标签:mini upd codeforce span int 次数 clu 还需要 rip
原文地址:https://www.cnblogs.com/Yuzao/p/8665982.html