旋转子段
旋转子段( \(\star\star \))
- 时限:\(1s\) 内存:\(256M\)
Descrption
- \(ZYL\) 有 \(N\) 张牌编号分别为 \(1, 2,……,N\)。他把这 \(N\) 张牌打乱排成一排,然后他要做一次旋转使得旋转后固定点尽可能多。
- 如果第 \(i\) 个位置的牌的编号为 \(i\),我们就称之为固定点。旋转可以被认为是将其中的一个子段旋转 \(180\) 度,这意味着子段的第一张牌和最后一张牌交换位置,以及第二张牌和倒数第二张牌交换位置,等等。写一个程序,找到旋转子段(子段长度可以为 \(1\))。
Input
- 第一行包含一个整数 \(N (1 ≤ N ≤100 000)\)。
- 第二行有 \(N\) 个数,第 \(i\) 个数表示旋转之前第 \(i\) 个位置的牌的编号。
Output
- 找到固定点最多的旋转所选的子段,输出旋转之后固定点的个数。
Sample Input
rotate.in
4
3 2 1 4
rotate.out
4
Sample Output
rotate.in
2
1 2
rotate.out
2
Hint
-
样例解释
在样例 \(1\) 中,只需要旋转的子段 \([3,2,1]\),将排列变成 \(1\ 2\ 3\ 4\),旋转后所有的牌都为固定点。答案为 \(4\)。
在样例 \(2\) 中,所有的牌已经在固定点,旋转子段 \([1]\) 或者子段 \([2]\),答案为 \(2\)。
数据范围
\(30\%\) 的数据满足:\(N ≤ 500\);
\(60\%\) 的数据满足:\(N ≤ 5000\);
\(100\%\) 的数据满足:\(1 ≤ N ≤ 500 000\)。
-
来源:
分析
- \(30\) 分很好求, \(n^3\) 暴力枚举即可,\(60\) 分也很好想,\(n\) 枚举旋转区间的中心,\(n\) 从小到大枚举旋转区间长度,\(100\) 分显然至少是 \(n*long(n)\) 。
- 假设最优旋转区间时 \([l,r]\) ,如果 \(a[l]!=r\ \&\&\ a[r]!=l\) ,显然区间 \([l+1,r-1]\)必然是最优旋转区间,依次类推,直到出现 \(a[l]==r\ ||\ a[r]==l\) ,此时旋转区间,端点对答案才有贡献。一个区间如果想成为最优解,那么必须满足左右端点之中最少有一个有反转过后成为不动点。
- 对于一个任意一个数 \(a[i]\) ,要想通过旋转满足 \(a[i]=i\) ,我们必须旋转的区间为 \([l=min(a[i],i),r=max(a[i],[i])]\) 。只有这个性质,对于区间两端的 \([1,l-1],[r+1,n]\) 的不动点我们可以用前缀和去维护,但区间 \([l,r]\) 之间旋转后有多少个不动点仍需要 \(n\) 的时间效率去求解,整体还是 \(n^2\) 。
- 通过样例分析,我们发现一个有趣的性质,对序列:\(3,2,1,4\) 最优旋转区间为 \([1,3]\) ,旋转后区间内有 \(3\) 个固定点,我们发现这三个固定点都满足 :\(a[i]+i==4\) ,即满足:值+下标 相等,我们发现如果满足 \(a[i]+i==a[j]+j\) 的点,我们以 \(a[i]\) 或 \(a[j]\) 为端点去旋转区间,肯定能同时满足 \(a[i],a[j]\) 同时成为固定点。进一步发现 如果我们以翻转的区间大小进行排序,如果区间大必然包含区间小的,所以对 \(a[i]+i\) 相同的点我们以旋转区间从小到大进行排序。
- 令 \(p=a[i]+i\) ,对 \(p\) 相同的如何求其旋转区间呢?显然, \(a[i]\) 为端点的旋转区间为:\([l=min(a[i],i),r=max(a[i],[i])]\) ,区间长度为 :\(len=abs(a[i]-i+1)\Rightarrow len=abs(p-2*i+1)\) 。
- 实现步骤:
- 预处理出 \(L[i],R[i]\) 分别表示 \(1\sim i\) 和 \(i\sim n\) 区间的不动点个数。
- 用 \(vector\) 把 \(p=a[i]+i\) 相同的点封装在同一个桶里,值为 \(i\) ,对每一个桶按区间大小升序排序。
- 具体实现见代码。
Code
#include <bits/stdc++.h>
const int maxn=5e5+5;
int L[maxn],R[maxn],a[maxn];
int n,p;
std::vector<int> q[maxn<<1];
bool Cmp(int x,int y){
return abs((x<<1)-p)<abs((y<<1)-p);
}
void Init(){
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
L[i]=(a[i]==i) ? L[i-1]+1:L[i-1];
q[a[i]+i].push_back(i);
}
for(int i=n;i>0;i--)
R[i]=(a[i]==i)? R[i+1]+1 : R[i+1];
}
void Solve(){
int ans=0;
for(int i=2;i<=(n<<1);++i){
if(!q[i].empty()){
p=i;
std::sort(q[i].begin(),q[i].end(),Cmp);
for(int j=0;j<q[i].size();++j){
int l=q[i][j];
int r=i-l;
if(l>r)std::swap(l,r);
ans=std::max(ans,L[l-1]+R[r+1]+j+1);
}
}
}
printf("%d\n",ans);
}
int main(){
Init();
Solve();
return 0;
}
hzoi