1. 程式人生 > 實用技巧 >【題解】 CF762E Timofey and remoduling 構造 剩餘系

【題解】 CF762E Timofey and remoduling 構造 剩餘系

Legend

給定一個 \(\bmod m\) 意義下的打亂順序的等差數列,保證各項不等,請找到它的首項和公差。

或者請輸出無解。

保證 \(m\) 為質數。 \(2 \le m \le 10^9+7\),陣列長度 \(1 \le n \le 10^5\)

\(\textrm{Link to}\) Codeforces

Editorial

我們先考慮有解的情況。

題目保證了各項不等,且 \(m\) 為質數,說明可以通過一次輪換將 \(\bmod m\) 的完全剩餘系內的數字進行輪換後,使得這個“等差數列”的公差變為 \(1\)

舉個例子,把 \(x=0,1,\cdots,4\) 帶入 \(3x+4 \pmod 5\)

會依次得到:\(4,2,0,3,1\)

我們如果對於對應位置的數字進行重新對映:

\(4 \to 0\)

\(2 \to 1\)

\(0 \to 2\)

\(\cdots\)

我們原來公差為 \(3\) 的等差數列就變成了公差為 \(1\) 的等差數列,並且這個等差數列在這個新的數域上是連續的

easy ver

考慮原題目如何進行答案檢驗:給你一個公差,但不告訴首項,如何檢驗答案的正確性?

當然,如果你知道了首項是哪一個數字,你就可以一項一項往後面推,就可以 \(O(n \log n)\) 使用 set 檢驗這個公差是否正確。

但如果不知道首項呢?是不是隨機選一個,先一項一項往後面推,再一項一項往前面推,看是不是所有數字都被用到呢?

然而是並不行的,因為先向某一個方向推進可能到了盡頭卻沒有停止,而是不小心使用了另一個方向的數字。

這樣子從另一個方向出發的時候就不能經過這個數字,會導致出問題。

不妨用之前的方法先進行對映,再考慮這個問題:

首先我們先按上面的方法將數字重新對映——這樣子答案公差就變成了 \(1\)。不過我們並不知道數字具體是如何對映的,

但我們知道它們被對映到了 \(0,1,2,\cdots,n-1\)

不過呢,可以發現,當 \(2n \le m\) 的時候是不會出現“數字重複使用”的問題的。因為一次推進最多隻會向某一側跳越 \(n-1\) 下,

而剩餘系中沒有被用到的數字是連續的,且沒有被用到的數字連續段長度至少有 \(m-n\)

,而 \(m-n>n-1\),所以說推進到不能推進的地方就會停止。

\(2n \le m\) 的情況下,我們選定了公差後向兩側推進就可以得到一條鏈,選擇多個不同的起點就可以得到多個不相交的鏈。

比如說當我們認定公差(對映後)是 \(3\) 時可能會得到:

\(0,3,6,\cdots\)

\(1,4,7,\cdots\)

\(2,5,8,\cdots\)

這樣三條鏈。

不難發現當選中的公差為幾,就會得到多少條不同的鏈。(前提是公差不大於 \(n\)

自然,當得到的鏈數量為 \(1\) 時,當前的公差便是正確的,否則不正確。

fix

那答案不對的情況下,有沒有辦法修正呢?

辦法是有的,將當前公差除以鏈的數量就行了。(前提是當前公差不大於 \(n\)

所以說,在 \(2n \le m\) 的情況下,我們已經知道此題怎麼做了:

隨機選兩個序列中的數字 \(x,y\),將 \(x-y\) 當做該等差數列的公差,並按照上述方法提取出鏈。

如果鏈的數量為 \(k\),真正的公差就是 \(\frac{x-y}{k}\),再用這個真正的公差找到首項就行了。

那麼,\(2n>m\) 時如何解決這個問題?

考慮補集轉化,我們選擇沒有出現過的數字來進行這個演算法,就可以轉化為 \(2n \le m\) 的情況。

我們求得了沒有出現過的數字的答案就可以反過來求得原問題的答案了。

具體來說,如果沒有出現過的數字的答案,首項是 \(st\),公差是 \(d\),則原問題的答案應該是:

首項是 \(st-d\),公差是 \(-d\)

Code

無解也就很好判斷了,按照上述演算法流程找到公差後,檢驗一遍答案是否正確即可。

#include <bits/stdc++.h>

#define debug(...) fprintf(stderr ,__VA_ARGS__)
#define __FILE(x)\
	freopen(#x".in" ,"r" ,stdin);\
	freopen(#x".out" ,"w" ,stdout)
#define LL long long

const int MX = 1e5 + 23;

int read(){
	char k = getchar(); int x = 0;
	while(k < '0' || k > '9') k = getchar();
	while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
	return x;
}

LL qpow(LL a ,LL b ,LL p){
	LL ans = 1;
	while(b){if(b & 1) ans = ans * a % p;
		a = a * a % p ,b >>= 1;
	}return ans;
}

int m ,n ,a[MX] ,A ,B;
bool solve(){
	
	std::set<int> S;
	for(int i = 1 ; i <= n ; ++i){
		S.insert(a[i]);
	}
	// std::random_shuffle(a + 1 ,a + 1 + n);
	if(n == 1){
		A = a[1] ,B = 1;
		return 1;
	}
	int d = (a[2] - a[1] + m) % m;

	int spl = 0;
	for(int i = 1 ; i <= n ; ++i){
		if(S.find(a[i]) != S.end()){
			++spl;
			for(int j = (a[i] + d) % m ; ; j = (j + d) % m){
				if(S.find(j) != S.end()){
					S.erase(j);
				}
				else break;
			}
			for(int j = a[i] ; ; j = (j - d + m) % m){
				if(S.find(j) != S.end()){
					S.erase(j);
				}
				else break;
			}
		}
	}

	d = 1LL * d * qpow(spl ,m - 2 ,m) % m;
	for(int i = 1 ; i <= n ; ++i){
		S.insert(a[i]);
	}
	
	for(int i = (a[1] + d) % m ; ; i = (i + d) % m){
		if(S.find(i) != S.end()){
			S.erase(i);
		}
		else break;
	}

	int good = 0;
	for(int i = a[1] ; ; i = (i - d + m) % m){
		if(S.find(i) != S.end()){
			good = i;
			S.erase(i);
		}
		else break;
	}

	if(S.empty()){
		A = good ,B = d;
		return 1;
	}
	else return 0;
}

int main(){
	__FILE(CF764E);
	m = read() ,n = read();
	if(n == m) return puts("0 1") ,0;

	for(int i = 1 ; i <= n ; ++i){
		a[i] = read();
	}
	if(n * 2 > m){
		std::set<int> S;
		for(int i = 1 ; i <= n ; ++i) S.insert(a[i]);
		n = 0;
		for(int i = 0 ; i < m ; ++i){
			if(S.find(i) == S.end()){
				a[++n] = i;
			}
		}
		if(solve()){
			printf("%d %d\n" ,(A - B + m) % m ,(m - B) % m);
		}
		else puts("-1");
	}
	else{
		if(solve()){
			printf("%d %d\n" ,A ,B);
		}
		else puts("-1");
	}
	return 0;
}