Search for a command to run...
Progressive hints first, then the full explanation and implementation when you're ready to cash out.
Review status
AI-generated and still unreviewed. Double-check the details before internalizing them.
Hints
Open only as much as you need to keep the solve alive.
For a revealed card , all the strategy noise collapses to one sentence: the winner is the owner of the smallest currently owned multiple of . The students choosing cards is just a fancy way to implement that.
Classify undealt cards first. If an undealt card has no initially owned multiple, it can never be won. If it does have one, it is always won. This is just transitivity of divisibility doing the heavy lifting.
Give every initially undealt card a random reveal time in . For a good undealt card , scan its multiples in increasing index. Ignore bad undealt multiples. Stop at the first initially owned multiple; anything after that is irrelevant.
At reveal time , a good zero multiple before the first initial multiple is already owned with probability encoded by its own distribution , while every earlier such candidate must have reveal time , giving factors of .
Do not store owner distributions for every student. Define moments , derive a linear recurrence, then run it backwards with scalar coefficients and add direct root contributions to the answer. That dodges the giant memory trap.
Key simplification
When card with value is revealed, a student matters only if they currently own some multiple of . That student chooses their smallest such multiple. Then the game picks the smallest chosen divisible value.
So globally, the winner is simply:
the owner of the smallest currently owned card such that .
Good. The classroom-card-game cosplay is gone; now it is a random process on divisibility.
Good and bad undealt cards
Call an initially undealt card good if some initially owned card is a multiple of it:
If is good, then whenever it is revealed, that initially owned multiple is already sitting there, so is definitely won.
If is not good, it can never be won. Suppose it were won by some zero card . Then was already won earlier, so following the ownership chain upward eventually reaches an initially owned card . Since every edge is divisibility,
which means had an initially owned multiple after all. Contradiction. Boom, fake complexity deleted.
Bad undealt cards are always discarded and can be ignored.
Random times
Instead of thinking about rounds, assign every initially undealt card an independent random reveal time in . Only relative order matters.
For a good undealt card , scan indices in increasing order and keep only multiples of .
During this scan:
Let:
If card is revealed at time , a candidate wins exactly when:
Let be the owner-distribution vector of card being already owned by time . Then the winner distribution at reveal time is
where is the unit vector for student .
The final owner distribution of card is therefore
Also,
Moments
Directly storing all vectors for all students would be bulky as hell. Instead define moments:
Now use Fubini:
Substitute the formula for :
Similarly, the final distribution of card is
All divisions are modulo using modular inverses.
Reverse DP trick
The recurrence is linear. Instead of computing vector-valued , store scalar coefficients saying how much the final answer needs each state.
Let be the coefficient of vector state in the final answer.
Initialize answers with initially owned cards. For each good undealt card , its final distribution says:
Then process states in increasing index. This is topological because every depends only on states with .
For a pending coefficient :
So:
The exponent never needs to exceed : every increase accounts for new lower-index good zero cards forced to appear after some reveal time, and those cards are all distinct. In code, allocating columns is plenty.
Complexity
We precompute divisibility in . The reverse DP has states and scans candidate lists, giving worst-case time and memory. With , that is exactly the kind of ugly-but-fine DP this problem wants.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MOD = 998244353;
ll modPow(ll a, ll e) {
ll r = 1;
while (e) {
if (e & 1) r = r * a % MOD;
a = a * a % MOD;
e >>= 1;
}
return r;
}
void addMod(int &x, ll v) {
x = int((x + v) % MOD);
}
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int main() {
setIO();
int n, m;
cin >> n >> m;
vector<ll> a(n);
for (ll &x : a) cin >> x;
vector<int> b(n);
for (int &x : b) cin >> x;
vector<vector<char>> divs(n, vector<char>(n, 0));
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
divs[i][j] = (a[j] % a[i] == 0);
}
}
vector<char> good(n, 0);
for (int i = n - 1; i >= 0; i--) {
if (b[i] != 0) {
good[i] = 1;
} else {
for (int j = i + 1; j < n; j++) {
if (b[j] != 0 && divs[i][j]) {
good[i] = 1;
break;
}
}
}
}
vector<vector<pair<int, int>>> cand(n);
vector<int> root(n, -1), rootZeros(n, 0);
for (int i = 0; i < n; i++) {
if (b[i] != 0 || !good[i]) continue;
int z = 0;
for (int j = i + 1; j < n; j++) {
if (!divs[i][j]) continue;
if (b[j] != 0) {
root[i] = j;
rootZeros[i] = z;
break;
}
if (good[j]) {
cand[i].push_back({j, z});
z++;
}
}
}
vector<int> inv(2 * n + 10);
for (int i = 1; i < (int)inv.size(); i++) {
inv[i] = (int)modPow(i, MOD - 2);
}
int D = n + 3;
vector<vector<int>> need(n, vector<int>(D, 0));
vector<int> ans(m, 0);
for (int i = 0; i < n; i++) {
if (b[i] != 0) {
addMod(ans[b[i] - 1], 1);
} else if (good[i]) {
for (auto [j, z] : cand[i]) {
addMod(need[j][z], 1);
}
addMod(ans[b[root[i]] - 1], inv[rootZeros[i] + 1]);
}
}
for (int i = 0; i < n; i++) {
if (b[i] != 0 || !good[i]) continue;
for (int d = 0; d < D; d++) {
int alpha = need[i][d];
if (alpha == 0) continue;
ll coef = 1LL * alpha * inv[d + 1] % MOD;
for (auto [j, z] : cand[i]) {
int nd = d + 1 + z;
addMod(need[j][nd], coef);
}
ll direct = coef * inv[d + 2 + rootZeros[i]] % MOD;
addMod(ans[b[root[i]] - 1], direct);
}
}
for (int i = 0; i < m; i++) {
if (i) cout << ' ';
cout << ans[i];
}
cout << '\n';
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MOD = 998244353;
ll modPow(ll a, ll e) {
ll r = 1;
while (e) {
if (e & 1) r = r * a % MOD;
a = a * a % MOD;
e >>= 1;
}
return r;
}
void addMod(int &x, ll v) {
x = int((x + v) % MOD);
}
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int main() {
setIO();
int n, m;
cin >> n >> m;
vector<ll> a(n);
for (ll &x : a) cin >> x;
vector<int> b(n);
for (int &x : b) cin >> x;
vector<vector<char>> divs(n, vector<char>(n, 0));
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
divs[i][j] = (a[j] % a[i] == 0);
}
}
vector<char> good(n, 0);
for (int i = n - 1; i >= 0; i--) {
if (b[i] != 0) {
good[i] = 1;
} else {
for (int j = i + 1; j < n; j++) {
if (b[j] != 0 && divs[i][j]) {
good[i] = 1;
break;
}
}
}
}
vector<vector<pair<int, int>>> cand(n);
vector<int> root(n, -1), rootZeros(n, 0);
for (int i = 0; i < n; i++) {
if (b[i] != 0 || !good[i]) continue;
int z = 0;
for (int j = i + 1; j < n; j++) {
if (!divs[i][j]) continue;
if (b[j] != 0) {
root[i] = j;
rootZeros[i] = z;
break;
}
if (good[j]) {
cand[i].push_back({j, z});
z++;
}
}
}
vector<int> inv(2 * n + 10);
for (int i = 1; i < (int)inv.size(); i++) {
inv[i] = (int)modPow(i, MOD - 2);
}
int D = n + 3;
vector<vector<int>> need(n, vector<int>(D, 0));
vector<int> ans(m, 0);
for (int i = 0; i < n; i++) {
if (b[i] != 0) {
addMod(ans[b[i] - 1], 1);
} else if (good[i]) {
for (auto [j, z] : cand[i]) {
addMod(need[j][z], 1);
}
addMod(ans[b[root[i]] - 1], inv[rootZeros[i] + 1]);
}
}
for (int i = 0; i < n; i++) {
if (b[i] != 0 || !good[i]) continue;
for (int d = 0; d < D; d++) {
int alpha = need[i][d];
if (alpha == 0) continue;
ll coef = 1LL * alpha * inv[d + 1] % MOD;
for (auto [j, z] : cand[i]) {
int nd = d + 1 + z;
addMod(need[j][nd], coef);
}
ll direct = coef * inv[d + 2 + rootZeros[i]] % MOD;
addMod(ans[b[root[i]] - 1], direct);
}
}
for (int i = 0; i < m; i++) {
if (i) cout << ' ';
cout << ans[i];
}
cout << '\n';
return 0;
}