原题链接:圈圈
解题思路:
1、首先序列的变化是由于队列中出现大于m的数字取模而改变序列的字典序最小而造成的,所以在序列中没有出现取模的时候序列不会变
2、确定最小字典序是通过找到最长公共序列来确定:比如101012的最小字典序为什么是第一个1而不是第三个1,此时我们寻找的最长公共子序列应该是10101,若第三个作为队首,此时得到的序列是121010,当然不是字典序最小。
3、通过二分枚举长度来找到最长的序列的长度
4、通过字符串哈希来存储目标序列,通过前缀和来寻找[l.r]之间的序列情况(类似于k进制数)
注意事项:
代码的初始化:1、最好是先去确定那些数字会因为+i而变化取模,这样会少很多次遍历寻找取模数
2、不用实现队列,我们只需要把数列长为n的复制一份(从n+1处)这样其实和队列的效果一样的。
参考代码
#include<iostream>
#include<vector>
using namespace std;
const int N=50010*2;
typedef unsigned long long ll;
long long base=131;
ll arr[N],ha[N],p[N];
int n,m,k,ans;
vector<int> v[N];
int find(int x,int y,int add)//当队列变化时,寻找x与y的最长公共子序列,返回下一次调整好的队首
{
int l=0,r=n;//枚举区间为0~n,长度取中间
while(l<r)
{
int mid = l+r+1>>1;//mid是枚举长度
if(ha[x+mid-1]-ha[x-1]*p[mid]==ha[y+mid-1]-ha[y-1]*p[mid]) l=mid;//若公共序列相等,则说明枚举区间太端,需要拉长区间
else r=mid-1;//否则压缩区间
}
if(l==n) return x;//区间长度是最大的n,此时说明以x与y开始的最长公共序列的长度是一样的,即返回x和返回y效果一样
if((arr[x+l]+add)%m<=(arr[y+l]+add)%m) return x;//若长度没有达到n,说明x和y长度不一样,则需要比较下一位谁最小,小的则为下次队列的队首
else return y;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin>>n>>m>>k;
for(int i=1;i<=n;i++)
{
cin>>arr[i];
arr[i+n]=arr[i];
v[(m-arr[i])%m].push_back(i);
}
p[0]=1;
for(int i=1;i<=2*n;i++) p[i]=p[i-1]*base;//进制数组,类似10的0次方为1,一次方为10,二次方为100
for(int i=1;i<=2*n;i++) ha[i]=ha[i-1]*base+arr[i];//字符串哈希 将目标数组通过哈希数组来存
ans=1;
for(int i=2;i<=n;i++)
{
ans= find(ans,i,0);//寻找第一次的队首
}
cout<<arr[ans+k-1]%m<<'\n';
for(int i=1;i<m;i++)
{
if(v[i].size())
{
ans=v[i][0];
for(int j=1;j<v[i].size();j++) ans=find(ans,v[i][j],i);
}
cout<<(arr[ans+k-1]+i)%m<<'\n';
}
return 0;
}最后欢迎大佬指正
0.0分
2 人评分
C语言网提供由在职研发工程师或ACM蓝桥杯竞赛优秀选手录制的视频教程,并配有习题和答疑,点击了解:
一点编程也不会写的:零基础C语言学练课程
解决困扰你多年的C语言疑难杂症特性的C语言进阶课程
从零到写出一个爬虫的Python编程课程
只会语法写不出代码?手把手带你写100个编程真题的编程百练课程
信息学奥赛或C++选手的 必学C++课程
蓝桥杯ACM、信息学奥赛的必学课程:算法竞赛课入门课程
手把手讲解近五年真题的蓝桥杯辅导课程
发表评论 取消回复