Yet Another Minimization Problem
题意:将序列划分为k段,每段的代价为这段所有重复数n(n-1)/2的和,求怎么分段使得,所有段的代价之和最小。
思路:容易知道动态规划方程式为dp[i][j]=max(dp[i-1][k]+cal(k+1,j)){i为分段数,j为序列总长度}但是这样做时间复杂度为O(n3),所以要对动态规划进行优化。决策单调性若序列总长度分别为i,m,i>m则最优决策点iu>=mu,根据决策点单调的性质分治优化,cal(i,j)则用莫队。
#include<iostream>
#include<fstream>
#include<cstring>
#include<cstdio>
using namespace std;
#define sc(x) scanf("%lld",&x);
#define ll long long
#define Inf 0x3f3f3f3f3f3f3f3f
const ll maxn=1e5+5;
ll dp[25][maxn];
ll L=1,R=1;
ll q[maxn],res=0,arr[maxn];
void update(ll c,ll pos)
{
res+=c*1LL*(q[arr[pos]])*(q[arr[pos]]-1)/2;
}
void read(ll *arr,ll n)
{
for(ll i=1;i<=n;i++)
{sc(arr[i]);}
q[arr[1]]++;
}
void out(ll *arr,ll n)
{
for(ll i=1;i<=n;i++)
printf("%d ",arr[i]);
printf("\n");
}
ll query(ll l,ll r)
{
while(L<l){update(-1,L);q[arr[L]]--;update(1,L);L++;}
while(L>l){L--;update(-1,L);q[arr[L]]++;update(1,L);}
while(R>r){update(-1,R);q[arr[R]]--;update(1,R);R--;}
while(R<r){R++;update(-1,R);q[arr[R]]++;update(1,R);}
return res;
}
//动态转移方程为 dp[j-1][k]=max(dp[j-1][i] +cal(i+1,k);
void dfs(ll L,ll R,ll l,ll r,ll layer)//分别是 人数范围 和 决策点的范围
{
if(L>R)return ;
ll mid=(L+R)>>1;//暂定人数为
ll res=Inf;ll k=-1;
for(ll i=l;i<=r;i++)//枚举可能的人数
{
if(dp[layer-1][i]+query(i+1,mid)<res){
res=dp[layer-1][i]+query(i+1,mid);
k=i;//决策点
}
}
dp[layer][mid]=res;
dfs(L,mid-1,l,k,layer);dfs(mid+1,R,k,r,layer);//分治
}
int main()
{
// freopen("C:\\Users\\123\\Desktop\\io.txt","r",stdin);
ll n,k;
sc(n);sc(k);
read(arr,n);
for(int i=0;i<25;i++)
for(int j=0;j<maxn;j++)
dp[i][j]=Inf;
dp[0][0]=0;
for(ll i=1;i<k;i++){
dfs(1,n,0,n,i);
}
ll res=Inf;
for(ll i=0;i<=n;i++){
res=min(res,dp[k-1][i]+query(i+1,n));
}
printf("%lld\n",res);
}