题目背景
ShadyPi和LPA最近很无聊, 他们在纸上玩起了序列游戏。
题目描述
LPA写下了一个由nn 个数字组成的序列, ShadyPi不断地将序列尾端的一个元素放在了序列的前端(例:1 2 3 4 5 可以变成5 1 2 3 4 ), 这样就得到了n个序列。
现在他会对这些序列进行m - 1次操作, 每次将所有序列的所有元素加一。但很快LPA就觉得无聊了, 于是他每次在ShadyPi将所有元素加一后将所有元素对m取模。 现在他们想请你回答:在ShadyPi和LPA没有操作时, 操作1次时, 操作2次时……操作n - 1次时所有序列字典序最小的一个的第k个元素是什么。
输入输出格式
输入格式:
第一行三个正整数, 表示n, m,k。
第二行n个正整数, 表示原序列的所有元素。
输出格式:
共m行, 每行一个整数, 表示每次操作后的答案。
输入输出样例
输入样例:
5 6 3
1 2 1 2 3
输出样例:
1
2
3
5
5
0
说明
对于 30%的数据,1≤n,m≤100;
对于 100%的数据,1≤n,m≤50000, 1≤k≤n, 0≤a[i]
解题分析
首先, 考虑30分算法: 暴力枚举每个序列, 每次更新时暴力修改所有序列中所有元素, 再暴力找出最小的序列输出…复杂度 O(N2) O ( N 2 ) 。
这样做会浪费大量时间,因为实际上每个序列有且仅有一次头元素变为0, 而若没有任何元素变为0则答案较前一轮一定只是 +1。所以我们可以在保存序列的时候先记录每个序列会在第几轮操作中变为0,在操作时只需要比较这些头元素变化为0的序列,输出字典序最小的一个。
现在我们需要处理的是如何快速比较两个序列。 考虑到所有序列的组成元素相同且排列顺序大致一样, 我们可以将原数组开为2倍长, 将同一元素分别存在 data[i] d a t a [ i ] 和 data[i+n] d a t a [ i + n ] , 避免访问取模后面的序列的元素。接下来我们可以将每个字符串取得哈希值。
假设原序列为a, b, a, b ,c,x 哈希基数为N,以查询长度为2为例, 那么其前一个的哈希值为:(可以将a看做操作变成的0)
我们有另一个序列 a, b, c, x, a, b, 其前一个的哈希值为:
如果我们要求它们前两个元素是否相同, 那么我们先将上述的每个哈希值乘以其查询长度与基数的乘积, 得到:
和
而这两个序列向后移查询长度减一的哈希值分别为:
和
即得到了向后跳两个元素的哈希值减去中间两个元素的值的哈希值(感性理解), 这样就可以用原序列向后跳查询长度减一的哈希值减去其而得到中间的两个数的哈希值,从而判断是否拥有相同公共前缀。
于是我们拥有了判断两个序列是否有一定长度的方法, 我们只需要二分枚举最长前缀的长度再判断是否符合要求即可找到第一个前缀不同的地方, 即可判断哪一个序列字典序更小。因为所有序列有且只有一次机会使得头元素为0,所以判定总复杂度上界为 O(NlogN) O ( N l o g N ) , 加上每个“所有元素加一”操作的 O(N) O ( N ) ,最终复杂度为 O(NlogN) O ( N l o g N ) 。
代码如下:
#include <cstdio>
#include <cstdlib>
#include <cctype>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define ul unsigned long long
#define ll long long
#define MX 100005
#define Key 19491001
namespace Hash
{
using std::swap;
using std::vector;
vector <int> zero[MX];
ll base[2];
ll key[2][MX], MOD[2];
ll data[MX];
ll hashval[2][MX];
int ans[MX], tot, dot, kth;
void prepare()
{
MOD[0] = Key;
MOD[1] = Key;
base[0] = 666233, base[1] = 1000000007;
key[1][0] = key[0][0] = 1;
for (R int i = 1; i < MX; ++i)
{
key[1][i] = key[1][i - 1] * base[1] % MOD[1];
key[0][i] = key[0][i - 1] * base[0] % MOD[0];
}
}
IN bool is_equal (int x, int y, const int &len)
{//这一步一定要理解透彻
if (!len) return true;
if (x > y) swap(x, y);
for (R int t = 0; t <= 1; ++t)
{
ll a = 0, b = 0;
if(x > 0) a = hashval[t][x - 1] * key[t][len] % MOD[t];
if(y > 0) b = hashval[t][y - 1] * key[t][len] % MOD[t];
ll a1 = (hashval[t][x - 1 + len] - a + MOD[t]) % MOD[t];
ll a2 = (hashval[t][y - 1 + len] - b + MOD[t]) % MOD[t];
if(a1 != a2) return false;
}
return true;
}
IN bool judge (const int &x, const int &y, const int &turn)
{//二分查找LCP
int lef = 0, rig = dot, mid, pos = -1;
W (lef <= rig)
{
mid = (lef + rig) >> 1;
if(is_equal(x, y, mid))
{pos = mid, lef = mid + 1;}
else rig = mid - 1;
}
if(pos == dot) return false;
if((data[x + pos] + turn) % tot < (data[y + pos] + turn) % tot) return true;
return false;
}
}
using namespace Hash;
int main()
{
prepare();
scanf("%d%d%d", &dot, &tot, &kth);
kth--;
for (R int i = 0; i < dot; ++i)
{
scanf("%d", &data[i]);
data[i + dot] = data[i];
zero[(tot - data[i]) % tot].push_back(i);
}
ll A[2];
A[0] = A[1] = 0;
for (R int i = 0; i < dot * 2; ++i)
{
for (R int t = 0; t <= 1; ++t)
{
A[t] = A[t] * base[t] % MOD[t];
A[t] = (A[t] + data[i]) % MOD[t];
hashval[t][i] = A[t];
}
}
int pos = 0;//处理原序列的情况
for (R int i = 1; i < dot; ++i)
if(judge(i, pos, 0)) pos = i;
ans [0] = data[kth + pos];
for (R int i = 1; i < tot; ++i)
{
if(zero[i].size() == 0) ans[i] = ans[i - 1] + 1;
//如果没有序列头部变为0的情况则当前情况答案为上一个答案加1
else
{
pos = zero[i][0];
for (R int j = 1; j < zero[i].size(); ++j)
{
if(judge(zero[i][j], pos, i)) pos = zero[i][j];
}
ans[i] = (data[kth + pos] + i) % tot;
}
}
for (R int i = 0; i < tot; ++i) printf("%d\n", ans[i]);
return 0;
}