一場 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\),發現這東西的一些性質:
- \(f(a)\bmod f(b)=f(a\bmod b)\)
- \(\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\)
考慮直接展開原式,時刻記住 \(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;
}