文章目录
- 题目描述
- 样例输入
- 样例输出
- 解析
- 代码
题目描述
给你一个数列,分成m段,每段的价值为相同数对的对数
求最小价值和
样例输入
10 2
1 2 1 2 1 2 1 2 1 2
样例输出
8
解析
容易想到区间dp
定义dp[i][j]:把前i个数分成j段的最小价值
那么枚举最后一段断点的位置,转移就是:
dp[i][j]=min(dp[i][j],dp[k][j-1]+calc(k+1,i)
(1<=k<i)
不难发现本题是符合决策单调性的
就可以利用这个进行优化啦
使用分治思路
还有一个问题是关于calc的计算
可以使用一个类似于简化的莫队的思路,维护两个指针即可
这样就完事啦
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+100;
int m,n;
int a[N],buc[N];
int l=1,r=0;
ll sum=0;
ll add(int x){return buc[a[x]]++;}
ll del(int x){return --buc[a[x]];}
ll calc(int nl,int nr){while(r<nr) sum+=add(++r);while(r>nr) sum-=del(r--);while(l>nl) sum+=add(--l);while(l<nl) sum-=del(l++);return sum;
}
ll dp[N][30];
int now;
void solve(int x,int y,int px,int py){if(x>y) return;int mid=x+y>>1,pl;
// printf("x=%d y=%d px=%d py=%d\n",x,y,px,py);dp[mid][now]=2e16;for(int i=px;i<=min(py,mid-1);i++){if(dp[mid][now]>dp[i][now-1]+calc(i+1,mid)){pl=i;dp[mid][now]=dp[i][now-1]+calc(i+1,mid);
// printf("mid=%d k=%d pl=%d dp=%d\n",mid,now,pl,dp[mid][now]);}}
// printf("x=%d y=%d px=%d py=%d mid=%d dp=%lld\n",x,y,px,py,mid,dp[mid][now]);solve(x,mid-1,px,pl);solve(mid+1,y,pl,py);
}
int main(){
// freopen("dp.in","r",stdin);
// freopen("dp.out","w",stdout);scanf("%d%d",&n,&m);for(int i=1;i<=n;i++){scanf("%d",&a[i]);}for(int i=1;i<=n;i++){dp[i][1]=calc(1,i);
// printf("i=%d k=1 dp=%lld\n",i,dp[i][1]);}for(int k=2;k<=m;k++){now=k;solve(1,n,0,n);//for(int i=1;i<=n;i++) printf("i=%d k=%d dp=%lld\n",i,k,dp[i][k]);}printf("%lld\n",dp[n][m]);
}
/*
10 2
1 2 1 2 1 2 1 2 1 25 2
1 1 1 1 1
*/