☰
又见循环移位
2026/10/2 21:08:34 网站建设 项目流程

题面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]整体旋转;
  • 把某个数插到前面,其他后移。

这些操作在减去下标或加上下标之后,往往就变成了纯粹的循环移位。


循环移位的两个关键性质

  1. 一个长度为 L 的循环移位,可以拆成若干次相邻交换,所以它能生成这个区间内的所有排列。
  2. 不同区间的循环移位组合起来,可以生成整个序列的任意排列(只要区间能覆盖所有元素)。

所以,一旦你证明“操作 = 循环移位”,你就可以直接说:这些元素可以任意重排。


常见信号

看到以下关键词,就要警惕是不是循环移位:

  • 操作里出现+1、-1、-(j-i)这类与下标差有关的项;
  • 操作把最后一个元素搬到前面,其他元素往后挪;
  • 操作后某个“差值”序列只是被重新排列了。

这时候,试着定义一个b_i = a_i - i或b_i = a_i + i,看看操作是不是变成了b的循环移位。


总结

循环移位之所以常见,是因为它把“位置变化”和“值变化”解耦了:

  • 原操作同时改变值和位置,很乱;
  • 减掉下标后,位置变化被抵消,只剩下值的重排;
  • 而重排意味着我们可以忽略位置,只关心值的集合。

所以以后看到“操作后某些差值只是换了位置”,就可以直接反应:循环移位 → 可任意重排 → 只看值。

这个直觉一旦建立,很多题都会变得简单。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询