1. 程式人生 > >Newcoder 2 C.圈圈(字串最小表示法+二分+hash)

Newcoder 2 C.圈圈(字串最小表示法+二分+hash)

Description

s h y shy 有一個佇列 a [ 1 ]

, a [ 2 ] , , a [ n
] a[1], a[2],…,a[n] 。現在我們不停地把頭上的元素放到尾巴上。在這過程中我們會得到 n n 個不同的佇列,每個佇列都是 a [
k ] , a [ k + 1 ] , , a [ n ] , a [ 1 ] , , a [ k 1 ] a[k],a[k+1],…,a[n],a[1],…,a[k-1]
的形式。在這些佇列中,我們可以找到字典序最小的。

s h y shy 無聊的時候會給佇列的每個元素加一玩。但是為了使得遊戲不這麼無聊, s h y shy 加一以後會給每個元素模 m m ,這樣子字典序最小的序列就會變了,生活就變得有趣。

很顯然這樣子加 m m 次以後,序列會變成原來的樣子。所以現在 s h y shy 想知道,在他沒有加一前,加一時,加二時,….,加 m 1 m-1 時字典序最小的序列的第 k k (和上面的 k k 沒有關係)個元素分別是幾。

Input

第一行三個整數 n , m , k n,m,k 表示序列長度,取模的數和要求的序列的第幾個元素。 接下來一行 n n 個整數表示初始序列。

( 1 n , m 5 1 0 4 , 1 k n , 0 a [ i ] < m ) (1\le n,m\le 5\cdot 10^4,1\le k\le n,0\le a[i]<m)

Output

m m 個整數表示答案。

Sample Input

5 6 3
1 2 1 2 3

Sample Output

1
2
3
5
5
0

Solution

字串最小表示法,但是對於選定了兩個起始位置需要快速判斷以著兩個位置開始的字串字典序大小關係,所以把整個串 h a s h hash ,之後二分求出使得兩個串字首相同的長度即可判斷兩個串字典序大小,假設當前加的是 i i ,之前最優位置是 x x ,若沒有位置在加 i i 之後變成 0 0 ,那麼最優位置依舊是 x x ,否則列舉所有這種加上 i i 變成 0 0 的位置來維護最優解即可,時間複雜度 O ( n l o g n ) O(nlogn)

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;
}