- 给定\(n\)本书,每本书有一个颜色。
- 每次操作你可以任选一本书移到最右边,问至少多少次操作能够让所有相同颜色的书都连在一起。
- \(n\le5\times10^5\)
动态规划
当选中若干移到右边的书之后,我们是可以任意决定它们的顺序的。
因此我们反向思考,去考虑留下来的书需要满足什么样的条件。
然后就发现,除了最右边留下来的一种颜色,其余颜色的书必须要满足连成一块,且这种颜色的书必须完全保留。
必须完全保留,也就意味着原本掺杂在这种颜色之间的所有书都必须右移。
于是我们记录每种颜色最左边的书、最右边的书、书的数量,就可以把每种颜色看成一个区间。
那么对于不在最右边的颜色,就是要满足选出权值总和尽可能大的完整区间,这种东西直接把所有区间按右端点排个序就可以愉快\(DP\)了。
然后对于最右边的颜色,假设我们已经选出的所有区间最大的右端点为\(R\),那么我们显然会选取\([R+1,n]\)中出现次数最多的书保留,这直接从后往前扫一遍,开桶维护预处理即可。
代码:\(O(nlogn)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 500000
using namespace std;
int n,a[N+5],f[N+5],Mn[N+5],Mx[N+5],tot[N+5],suf[N+5],cnt[N+5];
struct Data
{
int l,r,v;I Data(CI a=0,CI b=0,CI c=0):l(a),r(b),v(c){}
I bool operator < (Con Data& o) Con {return r<o.r;}//所有区间按右端点排序
}s[N+5];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define D isdigit(c=tc())
char c,*A,*B,FI[FS];
public:
I FastIO() {A=B=FI;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
}F;
int main()
{
RI i;for(F.read(n),i=1;i<=n;++i) F.read(a[i]),!Mn[a[i]]&&(Mn[a[i]]=i),Mx[a[i]]=i;//维护每种颜色最左边的书、最右边的书
for(i=n;i;--i) suf[i]=max(++cnt[a[i]],suf[i+1]);//倒枚一遍,开桶维护每个后缀最多的书的本数
RI t=0;for(i=1;i<=n;++i) Mx[i]&&(s[++t]=Data(Mn[i],Mx[i],cnt[i]),0);sort(s+1,s+t+1);//建区间,然后排序
RI p=1;for(i=1;i<=n;f[i]=max(f[i],f[i-1]),++i) W(s[p].r==i) f[i]=max(f[i],f[s[p].l-1]+s[p].v),++p;//双指针维护DP
RI ans=0;for(i=1;i<=n;++i) ans=max(ans,f[i]+suf[i+1]);return printf("%d\n",n-ans),0;//枚举最大的右端点统计答案
}