比 ARC184 簡單多了。
A. mod M Game 2
我們只關心 Alice 出的最後一張牌,這張牌會決定遊戲的勝負,因為除了最後一張牌,二人都可以選擇出牌來使得自己不會輸。
讓我們假設 Alice 最後出的牌為 \(x\),那麼在打出最後一張牌之前的牌之和為 \(n(n-1)-x\)。
- 若 \([n(n-1)-x]\bmod m=0\),令 \(y=n(n-1)\bmod m\),如果 Bob 打出的前 \(n-1\) 張牌中沒有 \(y\),那麼 \(x=y\),此時 Alice 會輸。可以被證明,Bob 一定存在一種策略能夠達到這種狀態。
- 若 \([n(n-1)-x]\bmod m\not=0\),此時 Alice 必勝。
條件 1 就等價於 \(1\le n(n-1)\bmod m\le n\)。
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
ll n, m, N;
int main() {
int t; cin>>t;
while(t--) {
scanf("%lld %lld", &n, &m); N = n;
n = n * (n + 1);
if(n % m >= 1 && n % m <= N) puts("Bob");
else puts("Alice");
}
return 0;
}
B. +1 and -1
貪心的想,我們想盡量讓最後的 \(A\) 中的元素儘量接近(平均值),令 \(s=\sum A_i\),那麼最終的序列 \(A=(\lfloor \frac{s}{n}\rfloor,\lfloor \frac{s}{n}\rfloor,\dots,\lfloor \frac{s}{n}\rfloor+1,\dots,\lfloor \frac{s}{n}\rfloor+1)\),其中 \(\lfloor \frac{s}{n}\rfloor+1\) 共有 \(s\bmod n\) 項。
之後我們就只需要判斷 \(A\) 是否可能透過操作變成上述的樣子,這是容易的。
詳細證明可以見 Atcoder 官解。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5+5;
int T, n, a[N], b[N], sum;
void solve() {
cin>>n; sum = 0;
for(int i = 1; i <= n; i++) cin>>a[i], sum += a[i];
for(int i = 1; i <= n; i++) {
b[i] = sum / n + ((n - i + 1) <= sum % n);
if(a[i] > b[i]) return puts("NO"), void();
int tmp = b[i] - a[i];
a[i] += tmp, a[i + 1] -= tmp;
}
puts("YES");
}
signed main() {
cin>>T;
while(T--) solve();
return 0;
}
C. Sum of Three Integers
題目只要求給出任意一組合法解。
讓我們列舉 \(i=1,2,\dots,n\),之後我們就只需要判斷是否有兩個整數 \(j,k\) 滿足 \(A_j+A_k=X-A_i\)。
我們可以透過一次卷積來解決這個問題。
具體的,我們可以透過一次卷積求出有多少個 \(A_j+A_k(1\le j,k\le n)\) 等於某個值 \(Y\),但是可能 \(j=k\),此時將每一個 \(A_p+A_p\) 的方案減去一。
除此之外,還可能存在 \(A_i+A_k=X-A_i\) 也就是一個 \(A_i\) 出現兩次的情況,我們可以先提前預處理出滿足這種情況的方案數,最後在減去即可。
時間複雜度 \(O(X\log X)\)。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 4e6 + 5;
const double Pi = acos(-1);
int n, x, m, lim = 1, l, r[MAXN];
long long p[MAXN], num[MAXN], cnt[MAXN], cnt0, maxn;
vector<int> ans;
struct Complex {
double x, y;
Complex() {}
Complex(double x, double y) : x(x), y(y) {}
Complex operator +(Complex t) {return Complex(x + t.x, y + t.y);}
Complex operator -(Complex t) {return Complex(x - t.x, y - t.y);}
Complex operator *(Complex t) {return Complex(x * t.x - y * t.y, x * t.y + y * t.x);}
} a[MAXN];
void fft(Complex *A, int opt) {
for(int i = 0; i < lim; i++) if(i < r[i]) swap(A[i], A[r[i]]);
for(int mid = 1; mid < lim; mid <<= 1) {
Complex w = Complex(cos(Pi / mid), opt * sin(Pi / mid));
for(int len = mid << 1, i = 0; i < lim; i += len) {
Complex p = Complex(1, 0);
for(int j = 0; j < mid; j++, p = p * w) {
Complex x = A[i + j], y = p * A[i + mid + j];
A[i + j] = x + y, A[i + mid + j] = x - y;
}
}
}
}
long long flg[MAXN], sum[MAXN];
int main() {
scanf("%d %d", &n, &x);
for(int i = 0; i < n; i++) {
scanf("%d", &p[i]);
maxn = max(maxn, p[i] * 2);
cnt[p[i]]++;
}
for(int i = 0; i < n; i++) if(x >= 2 * p[i]) sum[i] = (cnt[x - 2 * p[i]] - (p[i] == (x - 2 * p[i]))) * 2;
while(lim <= maxn) lim <<= 1, l++;
for(int i = 0; i < lim; i++) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (l - 1));
for(int i = 0; i < lim; i++) a[i].x = cnt[i];
fft(a, 1);
for(int i = 0; i < lim; i++) a[i] = a[i] * a[i];
fft(a, -1);
for(int i = 0; i < lim; i++) num[i] = (a[i].x / lim + 0.5);
for(int i = 0; i < n; i++) num[p[i] * 2]--;
for(int i = 0; i < n; i++) {
if(num[x - p[i]] - sum[i] >= 1) {
flg[p[n - 1]] = n - 1;
for(int j = n - 2; j > i; j--) {
if(flg[x - p[i] - p[j]]) {
printf("%d %d %d", i + 1, j + 1, flg[x - p[i] - p[j]] + 1);
return 0;
}
flg[p[j]] = j;
}
for(int j = i + 1; j <= n - 1; j++) flg[p[j]] = 0;
}
}
puts("-1");
return 0;
}
D. Random Walk on Tree
沒有場切。
先讓我們考慮 \(m=1\) 的情況。
此時 Takahashi 會在節點 \(0\) 和一個葉子節點之間反覆移動,顯然,Takahashi 想要給這 \(n\) 個葉子節點都染上色的期望往返次數為 \(\sum_{i=0}^{n-1}\frac{n}{n-i}\),每次往返需要 \(2\) 步,且最後一次不需要往返,所以期望步數為 \(2(\sum_{i=0}^{n-1}\frac{n}{n-i})-1\)。
接下來讓我們單獨考慮一條長度為 \(m\) 的鏈的情況,轉化為這樣一個問題:初始時 \(i=0\),每次隨機將 \(i\) 變為 \(i+1\) 或 \(i-1\),當 \(i=0\) 時只有第一種操作,求出 \(i\) 變為 \(m\) 的期望操作次數。
令 \(f_i\) 表示將 \(i\) 變為 \(i+1\) 的期望次數,令 \(g_i\) 表示將 \(i+1\) 變為 \(i\) 的期望次數,我們有:
- 當 \(i=0\) 時,有 \(f_0=g_0+1\)。
- 當 \(i=m-1\) 時,有 \(f_{m-1}=0,g_{m-1}=1\)。
- 當 \(1\le i\le m-1\) 時 \(f_i+g_{i-1}=f_{i+1}+g_i\),因為中間所有點的期望進出次數是相同的。
若我們固定了移動的起點,在每一個點時它進行兩種操作的機率時相同的,所以進行操作的期望次數與它具體是那種操作無關,於是有 \(f_i=g_{i-1}\)。
從大到小考慮每個點,有 \(f_i=m-i,g_i=m-i-1\),所以總期望次數為 \(m^2\)。
最後的答案就是 \((2(\sum_{i=0}^{n-1}\frac{n}{n-i})-1)m^2\)。
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const ll mod = 998244353;
ll qpow(ll a, ll b) {ll ans = 1; while(b) {if(b & 1) ans = ans * a % mod; a = a * a % mod; b >>= 1;} return ans;}
ll n, m, ans = 0;
int main() {
scanf("%lld %lld", &n, &m);
for(int i = 1; i <= n; i++) ans = (ans + n * qpow(i, mod - 2) % mod) % mod;
printf("%lld\n", (2 * ans + mod - 1) % mod * m % mod * m % mod);
return 0;
}
E. Adjacent GCD
依次列舉每一個 \(m=i=1,2,\dots n\) 來求解答案,記 \(m=i\) 的答案為 \(ans_i\)。
在我們不考慮 \(i\) 時,答案為 \(2ans_{i-1}\)。
現在讓我們考慮 \(i\),記子序列中的上一個選擇的數為 \(j\),那麼最新造成 \(\gcd(a_i,a_j)2^{j-1}\) 的貢獻。
也就是說 \(ans_i=2ans_{i-1}+\sum_{j=1}^{i-1}\gcd(a_i,a_j)2^{j-1}\)。
現在讓我們考慮後面那一坨玩意。考慮求出每個 gcd,然後再乘上 \(2^{j-1}\),但這樣算肯定不對,因為我們無法確定這個就是那兩個數的 gcd,無法保證它們沒有其它的質因子。
但是,我們知道 \(x=\sum_{d\mid x}\varphi(d)\)。
也就是說,我們只需要列舉 \(a_i\) 的每個因子 \(p\),看有多少個數包含這個因子 \(p\),那麼這個因子對答案造成的貢獻就為 \(p\cdot\varphi(p)\)。
時間複雜度 \(O(n\sqrt{a_i})\)。
//by Larrix
#include <bits/stdc++.h>
#define int long long
using namespace std;
constexpr int Mod = 998244353;
int n, prime[100005], phi[100005], cnt, rs, f[100005], fc[500005];
bool vis[100005];
void GetPrime() {
vis[1] = phi[1] = 1;
fc[1] = 2, fc[0] = 1;
for (int i = 2; i <= 100000; i = -~i) {
fc[i] = fc[i - 1] * 2 % Mod;
if (!vis[i]) prime[++cnt] = i, phi[i] = i - 1;
for (int j = 1; j <= cnt && i * prime[j] <= 100000; j = -~j) {
vis[i * prime[j]] = 1;
if (i % prime[j] == 0) phi[i * prime[j]] = phi[i] * prime[j];
else phi[i * prime[j]] = phi[i] * (prime[j] - 1);
if (i % prime[j] == 0) break;
}
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
GetPrime();
cin >> n;
fc[0] = 1;
for (int i = 1, x; i <= n; i++) {
fc[i] = fc[i - 1] * 2 % Mod;
cin >> x;
rs = rs * 2 % Mod;
for (int j = 1; j * j <= x; j++) if (x % j == 0) {
rs = (rs + f[j]) % Mod;
f[j] = (f[j] + phi[j] * fc[i - 1]) % Mod;
if (j * j == x) continue;
rs = (rs + f[x / j]) % Mod;
f[x / j] = (f[x / j] + phi[x / j] * fc[i - 1]) % Mod;
}
cout << rs << '\n';
}
return 0;
}