题意:给定一个序列,将其分成K段,每段元素不得全相同,求sigma(每段中不同元素差值的最小值)的最小值
n<=50000,k<=1000,序列中元素<=n,序列随机生成
既然序列是随机的,我们可以想到一个区间可能要延伸很长一段才会改变其中的最小差值,那么不妨将所有有贡献的点对都枚举出来(即:这个区间的两个端点之差是整个区间中最小的),问题就转化成了在数轴上有一些带权值的线段,选择K条不相交的使权值和最小,这个对于每个点枚举以他为结尾的线段进行转移即可。现在考虑如何求出所有点对。因为点对的分布很稀疏,暴力枚举就会枚举到大量无用的点对,那么不妨使用分块加速索引,每一块预处理出对于每一种权值块内元素中与其差值最小的是多少。那么对于每一个点,枚举后面的块,若最小差值能更新当前值就暴力在块内扫一遍,然后由于要保证两端点差值是最小的,因此枚举左端点时要从右往左,然后使用树状数组检查之前是否已经有点对右端点更靠左且差值更小即可。实测最后的点对数量是O(N)级别的。
代码:
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define gm 50001
#define abs _Abs
const int inf=0x7f7f7f7f;
int n,k,w,size,a[gm];
int c[gm];
int dis[gm][250];
inline int min(int a,int b){return a<b?a:b;}
inline int abs(int a){return a>>31?-a:a;}
struct e
{
int t;
e *n;
int c;
e(int t,e *n,int c):t(t),n(n),c(c){}
}*f[gm];
inline void push(int pos,int v)
{
for(;pos<=n;pos+=pos&-pos)
c[pos]=min(c[pos],v);
}
inline int get(int pos)
{
int v=inf;
for(;pos;pos-=pos&-pos)
v=min(v,c[pos]);
return v;
}
void setup()
{
memset(c,0x7f,n+1<<2);
static int b[250];
for(int i=1;i<=w;++i)
{
int l=(i-1)*size+1,r=min(i*size,n);
int top=0;
for(;l<=r;++l) b[++top]=a[l];
std::sort(b+1,b+top+1);
int p1=0,p2=1;
for(int j=1;j<=n;++j)
{
int &kre=dis[j][i]=inf;
while(p1<top&&b[p1+1]<j) ++p1;
while(p2<=top&&b[p2]<=j) ++p2;
if(p1!=0) kre=min(kre,j-b[p1]);
if(p2!=top+1) kre=min(kre,b[p2]-j);
}
}
}
void tour(int no,int block,int &ans,int beg)
{
int end=min(block*size,n);
for(;beg<=end;++beg)
{
int kre=abs(a[no]-a[beg]);
if(kre&&kre<ans)
{
ans=kre;
int cmp=get(beg);
if(ans<cmp)
{
push(beg,ans);
f[beg]=new e(no,f[beg],ans);
}
}
}
}
int F[gm][1001];
int DP()
{
memset(F,0x7f,sizeof F);
F[0][0]=0;
for(int i=1;i<=n;++i)
{
F[i][0]=0;
for(int j=1;j<=k;++j)
{
int &ans=F[i][j]=F[i-1][j];
for(e *l=f[i];l;l=l->n)
ans=min(ans,F[l->t-1][j-1]+l->c);
if(ans==inf) break;
}
}
return F[n][k];
}
int main()
{
scanf("%d%d",&n,&k);
size=ceil(sqrt(n));
w=(n-1)/size+1;
for(int i=1;i<=n;++i) scanf("%d",a+i);
setup();
for(int i=n;i;--i)
{
int j=(i-1)/size+1;
int len=inf;
tour(i,j,len,i+1);
for(++j;j<=w;++j)
if(dis[a[i]][j]<len)
tour(i,j,len,(j-1)*size+1);
}
printf("%d\n",DP());
return 0;
}