[CF1481E]Sorting Books
[CF1481E]Sorting Books
壹、题目描述 ¶
有 \(n\) 本书,第 \(i\) 本书的编号为 \(a_i\),你能执行以下操作:
- 任意抽取一本书,并将它放到最后;
请问在至少多少次操作之后,你能将具有相同编号的书放到一起?
数据范围与提示:\(n\le 5\times 10^5,a_i\in[1,n]\cap \mathbb Z\),然鹅并没有提示 😦
贰、题解 ¶
分多个思考层级,逐渐加深。
Logical Level #1 —— Arextre's Level
想一个问题,我们最多进行多少次操作可以让序列合法?显然至多 \(n\) 次(然鹅这个上界可能很松),即对于每本书,我们只会有 “操作它” 和 “不操它” 两种情况,对于需要 “操作它” 的数,我们显然能通过将所有操作排序之后让序列合法。
此时,我们的答案即为 “操作它” 的数个数之和。因而,我们只需要找到 “操作它” 的数最少即可。
考虑这个问题,显然,我们要直接找到 “操作它” 的数字,需要考虑到的东西有点多,比如说操作的先后、操作数的数量,事实上,对于后者处理还比较简单,但是对于操作的先后,我们没有比较好的处理方案,所以想要直接求解将会变得困难。
如何求解?考虑这种所有情况只属于两个集合的经典处理方式罢!
Logical Level #2 —— Beginner's Level
十分经典地,考虑正难则反,求解最多 “不操它” 的数字个数,对于 “操作它” 的数字,我们只需要只要他们需要被操作,至于怎么操作就不用管了,因为只要我们保证 “操作它” 的数字能让 “不操它” 的数字合法即可。
设 \(l_c,r_c\) 分别为数值 \(c\) 最左边出现的位置,最右边出现的位置,\(cnt_c\) 表示数值 \(c\) 出现的次数,定义 \(f_i\) 表示 \([i,n]\) 最多可以不操作的数的个数,我们考虑从后往前枚举 \(i\),对于第 \(i\) 的位置的 \(a_i\),如果我们想要不动它,有两种情况:
- \(i=l_{a_i}\),即这个位置为这个数字出现的最左端点,由于我们此时考虑的是没有动 \(a_i\) 的情形,所以,贪心地,我们显然可以将 \([l_{a_i},r_{a_i}]\) 的 \(a_i\) 都不动,即 \(f_i=cnt_{a_i}+f_{r_{a_i}+1}\);
- \(i\neq l_{a_i}\),这个位置并非 \(a_i\) 出现的最左端点,由于我们都没有动 \(a_i\),所以贪心地,将 \([i,n]\) 的所有 \(a_i\) 都不动才是最好的,此时 \(f_i=suf_{a_i}\),其中 \(suf_c\) 表示到位置 \(i\) 时,\([i,n]\) 出现 \(c\) 数值的个数;
但是还有一种情况,我们要操作 \(a_i\),所以 \(f_i\) 的另一个可能取值为 \(f_{i+1}\),在不同情况中取最大值即可。总的时间复杂度为 \(\mathcal O(n)\),还挺快的嘛。
如何想到?考虑一下如何将所有相同的数字放在一起的。
Logical Level #3 —— Medium Level
注:由于前面已经分析做法,所以下面不会再对解题有过多帮助。tl; dr,亦可直接跳过下面的部分。
继续分析,如果我们想要让一种数字全部挨在一起,一共就几种方案:
- 全部数字一开始都挨在一起;
- 将放在他们中间的数全部扔掉;
- 将他们全部连续地扔到后面去;
- 将前面的一些扔到后面去,再将后面不同的扔掉;
第一种情况是小概率事件,我们不管他。对于第二种和第三种也不作过多解释。对于第四种情况,其实就是第二、三种的组合。
但是我们可以从这个方案中的获得:如果相同的数字之间有其他的数,我们只有三种处理方法,这个操作数量似乎挺少的。
想要继续往前?分析一下后三种情况的相互关系。
Logical Level #4 —— High Level
这启发我们对于接下来的一些思考,如果一个数 \(a_i\) 被扔到后面去,它所面临的情况有两种:
- 当前最后的数字等于 \(a_i\);
- 当前最后的数字不等于 \(a_i\);
我们记当前最后的数为 \(x\),对于第二种,由我们之前的分析,我们针对 \(a_i\) 的处理方法就两种,一是把 \(x\) 又丢到后面去,二是把所有的 \(a_i\) 都往后面丢,这其实就是 “将他们全部连续地扔到后面去”。
针对第一种情况,其实质上是我们是想让 \(a_i\) 和前面的 \(a_i\) 拼在一起,也就是我们要将后面所有不同的数字都丢到后面去,一般地,对于 \(a_i\) 的出现的集合 \(A_i=\{i_1,i_2,i_3,\cdots i_k\}\),选择一个分界点 \(m\),将 \(i_1,...,i_m\) 的都丢到后面去,又将 \(i_{m+1}\) 之后与 \(a_i\) 不同的数字也丢到后面去了。
也就是说,这种复杂的情况实际上也可以被包含在三个十分简单的操作上,接下来就是如何利用这个性质的问题了。
Logical Level #5 —— Closestool's Level
现在的思路已经十分明了了,我们从前往后进行 \(\rm DP\),设 \(dp_i\) 表示前 \(i\) 个数字的不动点个数,假设当前访问到 \(i\)
- 如果 \(i\) 为 \(a_i\) 出现的右端点,当我们要保留他们的时候,保留的数就是 \(cnt_{a_i}+dp_{l_{a_i}-1}\),因为 \([l_{a_i},r_{a_i}]\) 之间和他们不同的数字都要扔掉;
- 如果 \(i\) 不是 \(a_i\) 出现的右端点,我们可以执行的就是 “将前面的一些扔到后面去,再将后面不同的扔掉”,那么我们保留下来的就是后面的 \(a_i\),以及 \(l_{a_i}\) 之前可以最大保留数,此时 \(f_i=suf_{a_i}+dp_{l_{a_i}-1}\);
当然,我们还可以考虑把所有的 \(a_i\) 都丢到后面去,此时的保留数即 \(f_i=f_{i-1}\). 择优取最大。
FAQ
Q. 如果 \(f_{i-1}\) 的状态记录的是保留 \(a_i\) 不动,而 \(f_i\) 又是 “把所有的 \(a_i\) 都丢到后面去” 怎么办?
A. 如果 \(a_{i-1}=a_i\),那么前一个状态是绝不可能存在 “保留 \(a_i\)” 的情况的。
然后就完了。
另:\(\sf closestool\) 总是直接想出正解,祂一般都会是最高等级的思维,因为已经没有人能打得过祂啦!
叁、参考代码 ¶
从后往前转移
#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<vector>
using namespace std;
// #define NDEBUG
#include<cassert>
namespace Elaina{
#define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
#define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define mmset(a, b) memset(a, b, sizeof a)
// #define int long long
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
template<class T>inline T fab(T x){ return x<0? -x: x; }
template<class T>inline void getmin(T& x, const T rhs){ x=min(x, rhs); }
template<class T>inline void getmax(T& x, const T rhs){ x=max(x, rhs); }
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
template<class T>inline void writc(T x, char s='\n'){
static int fwri_sta[1005], fwri_ed=0;
if(x<0) putchar('-'), x=-x;
do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
putchar(s);
}
}
using namespace Elaina;
const int maxn=5e5;
int a[maxn+5], n;
/** @brief the number of each kinds of books */
int cnt[maxn+5];
/** @brief the minimum index of the occurance of book @p i */
int l[maxn+5];
/** @brief the maximum index of the occurance of book @p i */
int r[maxn+5];
inline void input(){
n=readin(1);
rep(i, 1, n) a[i]=readin(1);
}
inline void init(){
rep(i, 1, n){
++cnt[a[i]];
if(!l[a[i]]) l[a[i]]=i;
r[a[i]]=i;
}
}
/** @brief the maximum number of books which stay unmoved */
int f[maxn+5], suf[maxn+5];
inline void getf(){
drep(i, n, 1){
++suf[a[i]];
if(i==l[a[i]]) f[i]=cnt[a[i]]+f[r[a[i]]+1];
else f[i]=suf[a[i]];
f[i]=max(f[i], f[i+1]);
}
writc(n-f[1]);
}
signed main(){
input();
init();
getf();
return 0;
}
从前往后转移
#include<bits/stdc++.h>
using namespace std;
#define MAXN 500005
#define lowbit(x) (x&-x)
#define reg register
#define mkpr make_pair
#define fir first
#define sec second
typedef long long LL;
typedef unsigned long long uLL;
const int INF=0x3f3f3f3f;
const int mo=1e9+7;
const int iv2=5e8+4;
const int lim=1000000;
const int jzm=2333;
const int orG=3,invG=332748118;
const double Pi=acos(-1.0);
typedef pair<int,int> pii;
const double PI=acos(-1.0);
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
_T f=1;x=0;char s=getchar();
while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
x*=f;
}
template<typename _T>
void print(_T x){if(x<0){x=(~x)+1;putchar('-');}if(x>9)print(x/10);putchar(x%10+'0');}
int gcd(int a,int b){return !b?a:gcd(b,a%b);}
int add(int x,int y){return x+y<mo?x+y:x+y-mo;}
int n,a[MAXN],head[MAXN],tail[MAXN],sum[MAXN],id[MAXN],dp[MAXN],b[MAXN],pre[MAXN],tot,ans;
bool cmp(int x,int y){return head[x]<head[y];}
signed main(){
read(n);for(int i=1;i<=n;i++)read(a[i]),sum[a[i]]++,id[i]=i;
for(int i=1;i<=n;i++){if(!head[a[i]])head[a[i]]=i;tail[a[i]]=i;}
for(int i=1;i<=n;i++)if(!head[i])head[i]=tail[i]=n+1;
sort(id+1,id+n+1,cmp);dp[0]=0;int j=0;
for(int i=1;i<=n;i++){
while(j<n&&head[id[j+1]]==i)
j++,dp[tail[id[j]]]=max(dp[i-1]+sum[id[j]],dp[tail[id[j]]]);
dp[n]=max(dp[n],dp[i-1]+sum[a[i]]-pre[a[i]]);pre[a[i]]++;
dp[i]=max(dp[i-1],dp[i]);
}
printf("%d\n",n-dp[n]);
return 0;
}