The Bakery
一道又好想又好写的题,太良心了。
很显然可以写出 \(f[x][y]=\max\limits_{i=0}^{x-1}f[i][y-1]+cost(i+1,x)\) 。自然想到以y为第一枚举关键字(毕竟它小啊),对于每一层就相当于在访问上一层的一个区间内的最小值,自然想到线段树。再看后面的贡献函数,可以想到对于每个新进来的点,考虑有哪些节点的cost会改变,显然只有对于在区间 \([i+1,x-1]\) 中没有出现过x的点i才会更新,而很明显i的最小值应该是x上一次出现的位置,预处理即可。于是我们需要区间加以及区间访问最小值,线段树。
#include<bits/stdc++.h>
//#define zczc
const int N=35010;
using namespace std;
inline void read(int &wh){
wh=0;int f=1;char w=getchar();
while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar();}
wh*=f;return;
}
inline int max(int s1,int s2){
return s1<s2?s2:s1;
}
int m,n,a[N],last[N],ll[N],f[N];
#define lc (wh<<1)
#define rc (wh<<1|1)
#define mid (t[wh].l+t[wh].r>>1)
struct node{
int l,r,lazy,data;
}t[N<<2];
inline void pushup(int wh){
t[wh].data=max(t[lc].data,t[rc].data);
}
inline void pushnow(int wh,int val){
t[wh].data+=val;
t[wh].lazy+=val;
}
inline void pushdown(int wh){
if(t[wh].lazy){
pushnow(lc,t[wh].lazy);
pushnow(rc,t[wh].lazy);
t[wh].lazy=0;
}
}
void build(int wh,int l,int r){
t[wh].l=l,t[wh].r=r,t[wh].lazy=t[wh].data=0;
if(l==r){
t[wh].data=f[l];
return;
}
build(lc,l,mid);
build(rc,mid+1,r);
pushup(wh);
return;
}
int work(int wh,int wl,int wr){
if(wl<=t[wh].l&&t[wh].r<=wr){
return t[wh].data;
}
int an=0;
pushdown(wh);
if(wl<=mid)an=max(an,work(lc,wl,wr));
if(wr>mid)an=max(an,work(rc,wl,wr));
pushup(wh);
return an;
}
void change(int wh,int wl,int wr,int val){
if(wl>wr)return;
if(wl<=t[wh].l&&t[wh].r<=wr){
pushnow(wh,val);return;
}
pushdown(wh);
if(wl<=mid)change(lc,wl,wr,val);
if(wr>mid)change(rc,wl,wr,val);
pushup(wh);return;
}
#undef lc
#undef rc
#undef mid
signed main(){
#ifdef zczc
freopen("in.txt","r",stdin);
#endif
read(m);read(n);
for(int i=1;i<=m;i++){
read(a[i]);ll[i]=last[a[i]];
last[a[i]]=i;
}
for(int i=1;i<=n;i++){
build(1,0,m);
memset(f,0,sizeof(f));
for(int j=1;j<=m;j++){
change(1,ll[j],j-1,1);
f[j]=work(1,0,j);
}
}
printf("%d",f[m]);
return 0;
}
一如既往,万事胜意