CF1527E 题解
思路
dp 题。
设 \(dp_{i,j}\) 表示前 \(i\) 个数分为 \(j\) 段的最小代价。
式子:
\(dp_{i,j}=\min_{k=1}^{i-1} (dp_{k,j-1}+w(k+1,i))\)
压一维:
\(dp_{i}=\min_{k=1}^{i} (dp_{k-1}+w(k,i))\)
其中,\(w(i,j)\) 表示题目中的 \(cost(sub(i,j))\)。
问题在于快速求 \(w(i,j)\)。
设 \(b_k=dp_{k-1}+w(k,i)\),则 \(dp_i=\min_{k=1}^{i} dp_k\)。每次转移需要求出数组 \(b\) 的最小值。每个 \(a_i\) 的对 \(b\) 贡献是 \(b_1\) 到 \(b_{lst_{a_j}}\) 加上 \(i-lst_{a_j}\),其中 \(lst_i\) 是数值 \(i\) 上次在 \(a\) 中出现的位置。
所以要维护 \(b\),区间加和最小值,使用线段树。
用上一轮的 \(dp\) 值建树,从 \(1\) 到 \(n\),每读进一个 \(a_i\),\(upd\) 更新线段树,\(que\) 询问最小值即为 \(dp_i\)。
\(O(kn\log n)\)。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=35010;
int n,k,a[maxn];
int tree[maxn<<2],tag[maxn<<2],dp[maxn];
int lst[maxn],pre[maxn];
void build(int nd,int l,int r){
if(l==r){
tree[nd]=dp[l-1];
return ;
}
int mid=l+r>>1,ls=nd<<1,rs=nd<<1|1;
build(ls,l,mid);build(rs,mid+1,r);
tree[nd]=min(tree[ls],tree[rs]);
}
void push(int nd,int l,int r){
int mid=l+r>>1,ls=nd<<1,rs=nd<<1|1;
tree[ls]+=tag[nd];
tag[ls]+=tag[nd];
tree[rs]+=tag[nd];
tag[rs]+=tag[nd];
tag[nd]=0;
}
void updata(int nd,int l,int r,int x,int y,int w){
if(l>=x&&r<=y){
tree[nd]+=w;
tag[nd]+=w;
return ;
}
if(tag[nd])push(nd,l,r);
int mid=l+r>>1,ls=nd<<1,rs=nd<<1|1;
if(x<=mid)updata(ls,l,mid,x,y,w);
if(y>mid)updata(rs,mid+1,r,x,y,w);
tree[nd]=min(tree[ls],tree[rs]);
}
int query(int nd,int l,int r,int x,int y){
if(l>=x&&r<=y){
return tree[nd];
}
if(tag[nd])push(nd,l,r);
int mid=l+r>>1,ls=nd<<1,rs=nd<<1|1;
if(y<=mid)return query(ls,l,mid,x,y);
if(x>mid)return query(rs,mid+1,r,x,y);
return min(query(ls,l,mid,x,y),query(rs,mid+1,r,x,y));
}
void sovle(){
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++){
lst[i]=pre[a[i]];
pre[a[i]]=i;
}
memset(dp,0x3f,sizeof(dp));dp[0]=0;
for(int i=1;i<=k;i++){
memset(tag,0,sizeof(tag));
build(1,1,n);
for(int j=1;j<=n;j++){
if(lst[j])updata(1,1,n,1,lst[j],j-lst[j]);
dp[j]=query(1,1,n,1,j);
}
}
cout<<dp[n]<<"\n";
}
int T;
signed main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
// cin.tie(0);cout.tie(0);
// ios::sync_with_stdio(false);
T=1;
while(T--)sovle();
}
套路
式子同样形如:\(dp_{i}=\max_{k=1}^{i} (dp_{k-1}+w(k,i))\)。
考虑每个 \(a_i\) 对 \(dp\) 的贡献。
int n,k,a[maxn];
int tree[maxn<<2],dp[maxn],tag[maxn<<2];
int pre[maxn],lst[maxn];
void sovle(){
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++){
lst[i]=pre[a[i]]+1;
pre[a[i]]=i;
}
for(int i=1;i<=k;i++){
memset(tree,0,sizeof(tree));
memset(tag,0,sizeof(tag));
build(1,1,n);
for(int j=1;j<=n;j++){
updata(1,1,n,lst[j],j);
dp[j]=query(1,1,n,1,j);
}
}
cout<<dp[n]<<"\n";
}