题面https://codeforces.com/contest/2266/problem/D
题解https://www.luogu.com.cn/article/pk0sh4ce
思路
Code:
voidsolve(){intn;cin>>n;vector<int>a(n+1,0);for(inti=1;i<=n;i++){cin>>a[i];a[i]-=i;}sort(a.begin()+1,a.end());a.erase(unique(a.begin()+1,a.end()),a.end());n=a.size();// for(int i=1;i<=n;i++)cout<<a[i]<<' ';// cout<<'\n';//把下标处理对!intt=1;intans=0;for(inti=1;i<n;i++){if(i+1<n&&a[i]+1==a[i+1])t++;else{ans=max(ans,t);t=1;}}ans=max(ans,t);cout<<ans<<'\n';return;}Conclusion
循环移位这个套路在竞赛里出现频率极高,因为它有一个非常强大的性质:
任意多个循环移位组合起来,可以得到任意排列。
也就是说,一旦你发现某个操作等价于“把一段循环移位”,你就可以认为这些元素可以随便重排,问题立刻简化成“只看元素的值,不看位置”。
为什么循环移位这么常见?
因为很多操作的本质就是“把某个东西挪到前面,其他往后挤”。
比如:
- 把最后一个元素移到最前面;
- 把区间
[i, j]整体旋转; - 把某个数插到前面,其他后移。
这些操作在减去下标或加上下标之后,往往就变成了纯粹的循环移位。
循环移位的两个关键性质
- 一个长度为 L 的循环移位,可以拆成若干次相邻交换,所以它能生成这个区间内的所有排列。
- 不同区间的循环移位组合起来,可以生成整个序列的任意排列(只要区间能覆盖所有元素)。
所以,一旦你证明“操作 = 循环移位”,你就可以直接说:这些元素可以任意重排。
常见信号
看到以下关键词,就要警惕是不是循环移位:
- 操作里出现
+1、-1、-(j-i)这类与下标差有关的项; - 操作把最后一个元素搬到前面,其他元素往后挪;
- 操作后某个“差值”序列只是被重新排列了。
这时候,试着定义一个b_i = a_i - i或b_i = a_i + i,看看操作是不是变成了b的循环移位。
总结
循环移位之所以常见,是因为它把“位置变化”和“值变化”解耦了:
- 原操作同时改变值和位置,很乱;
- 减掉下标后,位置变化被抵消,只剩下值的重排;
- 而重排意味着我们可以忽略位置,只关心值的集合。
所以以后看到“操作后某些差值只是换了位置”,就可以直接反应:循环移位 → 可任意重排 → 只看值。
这个直觉一旦建立,很多题都会变得简单。