1. 程式人生 > 其它 >一場 NOIP 模擬賽

一場 NOIP 模擬賽

模擬賽

日均一千題,題量破百萬!

上句為機房頂級魔怔人發言。

考前白嫖模擬賽當然要打,而且質量挺高的(叭

D 太毒瘤了所以跳了,B 卡不動常數了所以不卡了。

\(\mathcal A\)

給定質數 \(p\) 和正整數 \(a,b\),求最小正整數 \(x\) 使得 \((p^a-1)\equiv 1\pmod {p^b-1}\)

輸出 \(x\bmod 998244353\)\(a,b\leq 10^{18},p\leq 10^9\)

數論簽到?認真的嗎,雖然確實簽到成功了(

都知道 \(a\equiv 1\pmod b\) 的充要條件是 \((a,b)=1\),所以 \(p>2\) 統統無解。

\(f(a)=p^a-1\),發現這東西的一些性質:

  1. \(f(a)\bmod f(b)=f(a\bmod b)\)
  2. \(\gcd(f(a),f(b))=f(\gcd(a,b))\)

證了第一個就能推第二個了,根據樸素的輾轉相除即可證明。

對於 \(1\),已知 \(f(a)-p^{a-b}f(b)=f(a-b)\),所以取模等價於不斷的 \(f(a-kb)\),最終得到 \(f(a\bmod b)\)

然後就正常的 \(\text{exGcd}\) 沒啥大問題,唯一比較麻煩的是有一個 \(y=x-y\times\lfloor f(a)/f(b)\rfloor\)

\(\lfloor a/b\rfloor\)

的求解,可以化為 \((a-a\bmod b)/b\),如果 \(f(b)>0\) 的話直接求逆元就沒了,主要是 \(p^b=1\) 的情況。

考慮直接展開原式,時刻記住 \(p^b\equiv 1\pmod {998244353}\),且以下計算都在 \(\bmod 998244353\) 下進行。

\[\frac{p^a-p^{a\bmod b}}{p^b-1} \]\[=p^{a\bmod b}\times \frac{p^{\lfloor a/b\rfloor}\times b}{p^b-1} \]\[=p^{a\bmod b}\times \frac{p^{\lfloor a/b\rfloor}}{p^b-1} \]\[=p^{a\bmod b}+p^{a\bmod b+b}+\cdots +p^{a-b} \]\[=\lfloor a/b\rfloor\times p^{a\bmod b} \]

於是就可以簡單計算了。

但是得到的 \(x\) 有可能是真正的 \(x\),有可能是 \(x-(p^b-1)\)

而且 \((a,b,x,y)\)\(\text{exGcd}\) 有經典結論 \(|x|\leq b,|y|\leq a\),所以如果是後者,只需要簡單加上 \(p^b-1\)

這個結論可以用歸納法證,它也是把 int 丟進 \(\text{exGcd}\) 不會爆 int 的理論基礎。

又不難發現 \(x,y\) 正負交替,所以根據遞迴層數就可以判斷 \(x\) 的正負,於是做完了。

記得特判 \(y|x\) 的無解情況。

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const LL P = 998244353;

LL p, a, b;

LL read(){
	LL x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? - 1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

LL Gcd(LL a, LL b) {
	while(b) {LL c = a; a = b, b = c % b;}
	return a; 
}

LL Pow(LL a, LL b, LL p) {
	LL s = 1;
	for(; b; b >>= 1) {
		if(b & 1) s = s * a % p;
		a = a * a % p;
	}
	return s;
}

LL F(LL a) {return (Pow(2, a, P) + P - 1) % P;} 

LL Get(LL a, LL b) {
	LL y = F(b);
	if(! y) return (a / b) * Pow(2, a % b, P) % P;
	return (F(a) - F(a % b) + P) % P * Pow(y, P - 2, P) % P;
}

bool exGcd(LL a, LL b, LL &x, LL &y) {
	if(! b) {x = 1, y = 0; return false;}
	bool o = exGcd(b, a % b, x, y);
	LL z = x; x = y, y = ((z - y * Get(a, b) % P) % P + P) % P;
	return ! o;
}

int main() {
	p = read(), a = read(), b = read();
	if(p > 2 || !(a % b) || Gcd(a, b) != 1) {puts("-1"); return 0;}
	LL x, y;
	bool o = exGcd(a, b, x, y);
	if(o) x = (x + F(b)) % P;
	printf("%lld\n", (x % P + P) % P);
	return 0;
}

\(\mathcal B\)

給定長度為 \(n\) 的數列 \(a\) 和目標數列 \(b\),其中的數字都在 \([1,m]\) 中,在 \(\leq 60000\) 個操作內構造方案將 \(a\) 變成 \(b\)

一次操作形如 \((p,a_1,a_2,\cdots,a_m)\),表示將數列內從 \(p\) 開始的一個 \(m\) 的排列重新排成另一個 \(m\) 的排列 \(a\)

\(n\leq 200,m\leq 10\)

有解的充要條件是:一開始兩個數列就相等,或對應數字個數一樣 且 其中原本都至少有一個排列。

將需要改變的位置根據在 \(b\) 中的位置標記,利用排列不難交換相鄰項,根據氣泡排序就可以做到 \(O(n^2)\)

看著簡單寫起來要命,而且重度卡常,最後兩個點都是 \(67000\),大常數選手老淚縱橫(

#include<bits/stdc++.h>
using namespace std;

const int N = 210, M = 6e6 + 10;
int T, n, m, tot, p, q;
int a[N], b[N], s[N];
bool vis[N];
vector<int> sa[N], sb[N];

int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? - 1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}
struct Ans {int p, a[11];} ans[M];

void Clear() {
	tot = p = q = 0;
	for(int i = 1; i <= n; i ++) {
		a[i] = b[i] = s[i] = vis[i] = 0;
		sa[i].clear();
		sb[i].clear();
	}
}

void Back(int o) {
	if(! o) return;
	while(o --) {
		int v = a[p + m];
		ans[++ tot].p = p;
		ans[tot].a[1] = v;
		int now = 1;
		for(int i = 2; i <= m; i ++) {
			if(now == v) now ++;
			ans[tot].a[i] = now; now ++;
		}
		for(int i = 1; i <= m; i ++)
			a[p + i - 1] = ans[tot].a[i];
		s[p] = s[p + m];
		s[p + m] = 0;
		p ++;
	}
}

void Front(int o) {
	if(! o) return;
	while(o --) {
		int v = a[p - 1];
		ans[++ tot].p = p;
		ans[tot].a[m] = v;
		int now = 1;
		for(int i = 1; i <  m; i ++) {
			if(now == v) now ++;
			ans[tot].a[i] = now; now ++;
		}
		for(int i = 1; i <= m; i ++)
			a[p + i - 1] = ans[tot].a[i];
		s[p + m - 1] = s[p - 1];
		s[p - 1] = 0;
		p --;
	}
}

bool Sorted() {
	bool flag = true;
	for(int i = 1; i < n - m; i ++) if(s[i] > s[i + 1]) {flag = false; break;}
	return flag;
}

void Swap(int x, int y) {
	if(a[x] == a[y]) {swap(s[x], s[y]); return;}
	Front(p - y - 1);
	int v1 = a[x];
	int v2 = a[y];
	int now = 1;
	ans[++ tot].p = p;
	for(int i = 1; i <= m - 2; i ++) {
		while(now == v1 || now == v2) now ++;
		ans[tot].a[i] = now; now ++;
	}
	ans[tot].a[m - 1] = v1;
	ans[tot].a[m] = v2;
	for(int i = 1; i <= m; i ++) a[p + i - 1] = ans[tot].a[i];
	
	now = 1;
	ans[++ tot].p = p - 2;
	for(int i = 3; i <= m; i ++) {
		while(now == v1 || now == v2) now ++;
		ans[tot].a[i] = now; now ++;
	}
	ans[tot].a[1] = v2;
	ans[tot].a[2] = v1;
	for(int i = 1; i <= m; i ++) a[p - 2 + i - 1] = ans[tot].a[i];
	swap(s[x], s[y]);
}

void Swap_(int x, int y) {
	if(a[x] == a[y]) {swap(s[x], s[y]); return;}
	Back(x - m - p);
	int v1 = a[x];
	int v2 = a[y];
	int now = 1;
	ans[++ tot].p = p;
	for(int i = 3; i <= m; i ++) {
		while(now == v1 || now == v2) now ++;
		ans[tot].a[i] = now; now ++;
	}
	ans[tot].a[1] = v1;
	ans[tot].a[2] = v2;
	for(int i = 1; i <= m; i ++) a[p + i - 1] = ans[tot].a[i];
	
	now = 1;
	ans[++ tot].p = p + 2;
	for(int i = 1; i <= m - 2; i ++) {
		while(now == v1 || now == v2) now ++;
		ans[tot].a[i] = now; now ++;
	}
	ans[tot].a[m - 1] = v2;
	ans[tot].a[m] = v1;
	for(int i = 1; i <= m; i ++) a[p + 2 + i - 1] = ans[tot].a[i];
	swap(s[x], s[y]);
}

void Sort() {
	while(! Sorted()) {
		Back(n - m + 1 - p);
		for(int i = n - m - 1; i >= 1; i --)
			if(s[i] > s[i + 1]) Swap(i, i + 1);
		if(Sorted()) break;
		Front(p - 1);
		for(int i = m + 1; i < n; i ++)
			if(s[i] > s[i + 1]) Swap_(i, i + 1);
	}
}

int Get(int a[]) {
	int num[15];
	memset(num, 0, sizeof(num));
	for(int i = 1; i < m; i ++) num[a[i]] ++;
	for(int i = m; i <= n; i ++) {
		num[a[i]] ++;
		num[a[i - m]] --;
		bool flag = true;
		for(int j = 1; j <= m; j ++) if(! num[j]) {flag = false; break;}
		if(flag) return i - m + 1;
	}
	return 0;
}

void Work() {
	n = read(), m = read();
	Clear();
	for(int i = 1; i <= n; i ++) a[i] = read(), sa[a[i]].push_back(i);
	for(int i = 1; i <= n; i ++) b[i] = read(), sb[b[i]].push_back(i);
	for(int i = 1; i <= m; i ++)
		if(sa[i].size() != sb[i].size()) {puts("NO"); return;}
	bool flag = true;
	for(int i = 1; i <= n; i ++) if(a[i] != b[i]) {flag = false; break;}
	if(flag) {puts("YES"); puts("0"); return;}

	p = Get(a), q = Get(b);
	if(! p || ! q) {puts("NO"); return;}
	
	for(int i = 1; i <= m; i ++)
		random_shuffle(sb[i].begin(), sb[i].end());
	
	Back((n - m + 1) - p);
	for(int i = 1; i <= n - m; i ++) {
		int v = a[i];
		for(int j = 0; j < (int) sb[v].size(); j ++) {
			int p = sb[v][j];
			if(vis[p] || (p >= q && p <= q + m - 1)) continue;
			s[i] = p, vis[p] = true; break;
		}
	}
	Sort();

	if(p < q) Back(q - p);
	if(p > q) Front(p - q);
	
	ans[++ tot].p = p;
	for(int i = 1; i <= m; i ++) ans[tot].a[i] = b[q + i - 1];
	
	puts("YES");
	int sum = 0;
	for(int i = 1; i <= tot; i ++) sum += (i == tot || ans[i].p != ans[i + 1].p);
	printf("%d\n", sum);
	for(int i = 1; i <= tot; i ++) if(i == tot || ans[i].p != ans[i + 1].p) {
		printf("%d ", ans[i].p);
		for(int j = 1; j <= m; j ++) printf("%d ", ans[i].a[j]);
		puts("");
	} 
}

int main() {
	int T = read();
	while(T --) Work();
	return 0;
}

\(\mathcal C\)

給定 \(n\) 個節點 \(m\) 條邊的無向圖,每條邊有初始權值 \(0/1\)

每次可以翻轉一條邊,同時翻轉所有和它有共同端點的邊(自己只翻轉一次),求構造翻轉方案使得所有邊都為 \(0\)

資料保證有解,\(n\leq 1000,m\leq n\times (n-1)/2\)

直接用邊異或消元可以得到 50pts 的好成績,必須考慮把消元扔到點上。

考慮一個點為 \(1\) 表示將與它相連的邊都翻轉,那麼一次邊的翻轉可以視作 \(u,v,(u,v)\) 兩點一邊的翻轉。

全部為 \(0\) 的充要條件是 每個點 和 與它相連的所有邊 的異或和為 \(0\)

考慮將每個點的這個異或和記為 \(E\),能影響到它的點記為 \(V\)

對於邊 \((u,v)\),顯然 \(E(u)\to v,E(v)\to u\),然後對於所有度數為偶數的點 \(E(u)\to u\)

因為將這個點翻轉後,點權 與 所有相連的邊權 的異或和必變。(奇度點則必不變)

然後所有 \(V\),初始只有 \(V(u)\to u\)

然後兩個同步消元,得到影響每個點的其他點。

然後根據初始連邊情況判斷一個點需不需要翻轉,要的話就將所有能影響到它的點翻轉。

最後把要翻轉的點翻轉一邊就知道了每條邊要不要翻轉了。

利用 bitset 優化是基本操作了,時間複雜度 \(O(n^3/w)\)

#include<bits/stdc++.h>
using namespace std;

const int N = 1010, M = N * N;
int n, m, a[M];
int d[N], d1[N], d2[N];
vector<int> G[N];
bitset<N> E[N], V[N];

int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? - 1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

int main() {
	n = read(), m = read();
	for(int i = 1; i <= m; i ++) {
		int u = read(), v = read(); a[i] = read();
		E[u].flip(v);
		E[v].flip(u);
		G[u].push_back(i);
		G[v].push_back(i);
		if(a[i])
			d1[u] ^= 1, d1[v] ^= 1;
		d[u] ^= 1, d[v] ^= 1;
	}
	for(int i = 1; i <= n; i ++) {
		if(! d[i]) E[i].flip(i);
		V[i].flip(i);
	}
	for(int i = 1; i <= n; i ++) {
		int k = 0;
		for(int j = i; j <= n; j ++) if(E[j][i]) {k = j; break;}
		if(! k) continue;
		swap(E[k], E[i]);
		swap(V[k], V[i]);
		for(int j = 1; j <= n; j ++) if(j != i && E[j][i]) E[j] ^= E[i], V[j] ^= V[i];
	}
	for(int i = 1; i <= n; i ++) if(d1[i])
		for(int j = 1; j <= n; j ++) if(V[i][j]) d2[j] ^= 1;
	for(int i = 1; i <= n; i ++) if(d2[i])
		for(int u : G[i]) a[u] ^= 1;
	int tot = 0;
	for(int i = 1; i <= m; i ++) if(a[i]) tot ++;
	printf("%d\n", tot);
	for(int i = 1; i <= m; i ++) if(a[i]) printf("%d ", i); puts("");
	return 0;
}

$$\mathcal{NOIP2021\ RP++}$$