版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/V5ZSQ/article/details/83758362
Description
有一个队列 。现在我们不停地把头上的元素放到尾巴上。在这过程中我们会得到 个不同的队列,每个队列都是 的形式。在这些队列中,我们可以找到字典序最小的。
无聊的时候会给队列的每个元素加一玩。但是为了使得游戏不这么无聊, 加一以后会给每个元素模 ,这样子字典序最小的序列就会变了,生活就变得有趣。
很显然这样子加 次以后,序列会变成原来的样子。所以现在 想知道,在他没有加一前,加一时,加二时,….,加 时字典序最小的序列的第 (和上面的 没有关系)个元素分别是几。
Input
第一行三个整数 表示序列长度,取模的数和要求的序列的第几个元素。 接下来一行 个整数表示初始序列。
Output
个整数表示答案。
Sample Input
5 6 3
1 2 1 2 3
Sample Output
1
2
3
5
5
0
Solution
字符串最小表示法,但是对于选定了两个起始位置需要快速判断以着两个位置开始的字符串字典序大小关系,所以把整个串 ,之后二分求出使得两个串前缀相同的长度即可判断两个串字典序大小,假设当前加的是 ,之前最优位置是 ,若没有位置在加 之后变成 ,那么最优位置依旧是 ,否则枚举所有这种加上 变成 的位置来维护最优解即可,时间复杂度
Code
#include<cstdio>
#include<vector>
using namespace std;
typedef unsigned long long ull;
const int maxn=100005;
int n,m,k,a[maxn];
ull base=100007,b[maxn],h[maxn];
vector<int>v[maxn];
int Solve(int s1,int s2,int add)
{
int l=0,r=n,mid,ans=0;
while(l<=r)
{
mid=(l+r)/2;
if(h[s1]-h[s1+mid]*b[mid]==h[s2]-h[s2+mid]*b[mid])ans=mid,l=mid+1;
else r=mid-1;
}
if(ans==n)return s1;
return (a[s1+ans]+add)%m<(a[s2+ans]+add)%m?s1:s2;
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
k--;
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
a[i+n]=a[i];
v[m-a[i]].push_back(i);
}
b[0]=1;
for(int i=1;i<=2*n;i++)b[i]=b[i-1]*base;
for(int i=2*n-1;i>=0;i--)h[i]=h[i+1]*base+a[i];
int ans=0;
for(int i=1;i<n;i++)ans=Solve(ans,i,0);
printf("%d\n",a[ans+k]);
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=Solve(ans,v[i][j],i);
}
printf("%d\n",(a[ans+k]+i)%m);
}
return 0;
}