R19 - A 过去
题目描述
你有一个长度为 \(n\) 的排列 \(p_1,p_2\sim p_n\),初始时 \(p_i=i\)。
接下来你进行了 \(m\) 次操作,第 \(i\) 次操作翻转了前缀 \(p_1,p_2\sim p_{a_i}\),即如果记 \(b_j=p_j\),那么新的 \(p_1\sim p_{a_i}\) 满足 \(p_j=b_{a_i-j+1}\)。
你需要输出最后每个位置的值,保证对于 \(i=1,2\dots m-1\),\(a_i\leq a_{i+1}\)。
Solution
做法很多。考虑反转之后下标的变化,发现经过多次翻转之后,下标最后是一个奇偶交替的形式,维护一些前缀和就好。
Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e7 + 5, mod = 998244353;
int n, m, seed, sum[N], num[N], ans[N];
uint64_t PRG_state;
uint64_t get_number(){PRG_state ^= PRG_state << 13;PRG_state ^= PRG_state >> 7;PRG_state ^= PRG_state << 17;return PRG_state;
}
int readW(int l,int r){return get_number() % (r - l + 1) + l;
}
int pw(int x){return (x & 1 ? -1 : 1); }
signed main(){ios::sync_with_stdio(0);cin.tie(0), cout.tie(0);cin >> n >> m >> seed;PRG_state = seed;for(int i = 1; i <= m; ++i){int x = readW(1, n);num[x]++;}int tot = 0;for(int i = 1; i <= n; ++i){for(int j = 1; j <= num[i]; ++j){++tot;sum[i] += pw(tot) * i;}}int k = pw(m);for(int i = n; i >= 1; --i){// cout << sum[i] << ' ' << num[i] << '\n';sum[i] += sum[i + 1], num[i] += num[i + 1];int pos = (num[i] ? k * sum[i] + pw(num[i] & 1) * i + (num[i] & 1) : i);// assert(pos >= 0);// cout << i << ' ' << pos << '\n';// if(pos < 0) cout << "!!!!!!", exit(0);ans[pos] = i;}int tmp = 1, res = 0;for(int i = 1; i <= n; ++i){(tmp *= n) %= mod;(res += tmp * ans[i]) %= mod;}cout << res;return 0;
}
R19 - B 白云
题目描述
我们定义云朵函数
现在给定一个奇数 \(m\),请你构造一个正整数 \(n (n\leq 10^{18})\),使得 \(f(n)=m\),或者报告无解。
我该如何构造?我问我自己。
Solution
玄学题。首先 \(f(n) = n - \varphi(n)\)。
先考虑两个不同的奇数 \(p\), \(q\),那么 \(f(pq) = pq - \varphi(pq) = pq - (p - 1)(q - 1) = p + q - 1\)。
令他 \(=m\),由于哥德巴赫猜想 \(p + q = m + 1\),\(m + 1\) 是偶数,所以可以拆成两个质数的和。但是可没说可以拆成两个不同质数的和。打表之后发现特判一些小的情况之后,\(\le 1e9\) 的情况下就大部分是对的了。
遍历 1 ~ 1e9,如果找到一个 \(i\),\(i\) 和 \(m + 1 - i\) 都是质数就直接退出就好了。本题数据范围下跑的很快。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;mt19937_64 rnd(time(0));
ll gen(ll l,ll r)
{return rnd()%(r-l+1)+l;
}
// 快速乘 (防止溢出)
ll mul_mod(ll a, ll b, ll mod) {ll res = 0;while (b) {if (b & 1) res = (res + a) % mod;a = (a + a) % mod;b >>= 1;}return res;
}// 快速幂
ll pow_mod(ll a, ll n, ll mod) {ll res = 1;while (n) {if (n & 1) res = mul_mod(res, a, mod);a = mul_mod(a, a, mod);n >>= 1;}return res;
}// Miller-Rabin 素性测试,判断一个数是不是素数
bool is_prime(ll n) {if (n < 2) return false;if (n == 2 || n == 3) return true;if (n % 2 == 0) return false;// 将 n-1 分解为 d*2^rll d = n - 1;int r = 0;while (d % 2 == 0) {d /= 2;r++;}// 测试的底数vector<ll> bases;if (n < 4759123141LL) {bases = {2, 7, 61};} else {bases = {2, 325, 9375, 28178, 450775, 9780504, 1795265022};}for (ll a : bases) {if (a % n == 0) continue;ll x = pow_mod(a, d, n);if (x == 1 || x == n - 1) continue;bool composite = true;for (int i = 0; i < r - 1; i++) {x = mul_mod(x, x, n);if (x == n - 1) {composite = false;break;}}if (composite) return false;}return true;
}// Pollard Rho 算法
ll pollard_rho(ll n) {if (n % 2 == 0) return 2;if (n % 3 == 0) return 3;ll x = gen(1LL, n - 1);ll y = x;ll c = gen(1LL, n - 1);ll d = 1;auto f = [&](ll x) { return (mul_mod(x, x, n) + c) % n; };while (d == 1) {x = f(x);y = f(f(y));d = __gcd(abs(x - y), n);if (d == n) return pollard_rho(n);}return d;
}// 质因数分解
void factorize(ll n, unordered_map<ll, int>& factors) {if (n == 1) return;if (is_prime(n)) {factors[n]++;return;}ll d = pollard_rho(n);factorize(d, factors);factorize(n / d, factors);
}ll m;
int main(){cin.tie(0)->sync_with_stdio(0);cin >> m;if(is_prime(m)) return cout << m * m, 0;++m;for(int i = 2; i <= m; ++i){if(i != m - i && is_prime(i) && is_prime(m - i)){cout << i * (m - i);return 0;}}return 0;
}
R19 - C 朦胧
题目描述
朦胧之间,无数的光影交错,你发现有 \(m\) 条光线在树上显现,第 \(i\) 条光线的颜色覆盖了树上从 \(x_i\) 到 \(y_i\) 路径上的所有点,并且具有能量值 \(w_i\)。
你想要攀爬这巨树,并提出了 \(q\) 个方案,在第 \(i\) 个方案下你想要知道从树上的结点 \(u_i\) 走到 \(v_i\),经过的所有光线的能量值之和是多少?
注意,一条光线经过多次也只算一次。
Solution
Excise Route P那里提到了本体做法。
注意差分之后,要先前缀和一遍求出真实值,再前缀和一边求出前缀和,然后差分查询。
Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
typedef pair<int, int> pii;
const int N = 3e5 + 5;
int dep[N], f[N][25], n, m, q, d[N], g[N];
vector<int> e[N];
void init(int u, int fa){dep[u] = dep[fa] + 1;f[u][0] = fa;for(int i = 1; (1 << i) <= dep[u]; ++i) f[u][i] = f[f[u][i - 1]][i - 1];for(int v : e[u]){if(v != fa){init(v, u);}}
}
int lca(int u, int v){if(dep[u] < dep[v]) swap(u, v);for(int i = 19; i >= 0; --i){if(dep[f[u][i]] >= dep[v])u = f[u][i];} if(u == v) return u;for(int i = 19; i >= 0; --i){if(f[u][i] != f[v][i]){u = f[u][i], v = f[v][i];}}return f[u][0];
}
void dfs(int u, int fa){for(int v : e[u]){if(v != fa){dfs(v, u);d[u] += d[v];g[u] += g[v];}}
}
void get(int u, int fa){for(int v : e[u]){if(v != fa){d[v] += d[u];g[v] += g[u];get(v, u);}}
}
signed main(){ios::sync_with_stdio(0);cin.tie(0), cout.tie(0);// freopen("hazy1.in", "r", stdin);// freopen("hazy.out", "w", stdout);cin >> n >> m >> q;for(int i = 1; i < n; ++i){int u, v;cin >> u >> v;e[u].emplace_back(v);e[v].emplace_back(u);}init(1, 0);for(int i = 1; i <= m; ++i){int u, v, w;cin >> u >> v >> w;int L = lca(u, v);d[u] += w, d[v] += w, d[L] -= w, d[f[L][0]] -= w;g[u] -= w, g[v] -= w, g[L] += 2 * w;}dfs(1, 0);// for(int i = 0; i <= n; ++i) cout << d[i] << ' ' << g[i] << '\n';get(1, 0);// for(int i = 0; i <= n; ++i) cout << d[i] << ' ' << g[i] << '\n';d[0] = g[0] = 0;while(q--){int u, v;cin >> u >> v;int L = lca(u, v);// cout << L << ' ' << d[u] << ' ' << d[v] << ;int ans = d[u] + d[v] - d[L] - d[f[L][0]] + g[u] + g[v] - 2 * g[L];cout << ans << '\n';}return 0;
}
R19 - D 停留
题目描述
命运的枝丫将我们连接在一起,是牵挂,还是枷锁。
仿佛那过往的人生是由 \(n\) 个结点构成的无向图,有一些边将不同的结点连接在一起:
-
如果某一对结点 \(i,j(i<j)\) 满足 \(j=i+x\),那么我们就称 \(i\) 可以追到 \(j\)。
-
如果某一对结点 \(i,j(i<j)\) 满足 \(i=j-y\),那么我们就称 \(j\) 可以忆到 \(i\)。
如果某一对结点 \((i,j)\) 满足 “\(i\) 可以追到 \(j\) 或者 \(j\) 可以忆到 \(i\)”,结点 \(i,j\) 之间就会有一条无向边。
时间在侵蚀这一切,而现在原先的那些边中也只剩下 \(m\) 条了,第 \(i\) 条连接了 \(u_i,v_i\)。
为了留住残存的希望,你需要选择一些边施加封存魔法,而剩下的边会随着岁月消逝。
但是由于某种禁忌,你不能选择两条具有公共顶点的边施加魔法,否则会引发不可逆转的后果。
那么现在你想要知道,有多少种不同的施加魔法的方法,使得不触犯禁忌?两种方式不同,当且仅当某条边在一种方案里被施加魔法而另一种没有。
由于答案可能很大,请输出答案对 \(998244353\) 取模的结果。
Solution
原题。
令 \(a = \min(x, y)\),\(b = \max(x, y)\)。对于 \(a = b\) 的情况把 \(b\) 改成 \(n\) 或者别的什么大一点的数都行。
那么对于 \(a < b\) 的情况。考虑 \(d = \gcd(a, b)\),那么模 \(d\) 不同的点之间肯定互不影响,把各个同余类拆开做,最后再乘起来就好。
那么现在令 \(A \gets \frac{a}{d}, B \gets \frac{b}{d}\),那么 \(\gcd(A, B) = 1\)。
边独立集没有什么好的做法,只能状压。那么我们考虑转化网格图,因为这样我们总可以状压短边,以降低复杂度。具体来说,我们建立 \(f : \mathbb{R} \to {\mathbb{R}}^2\)。规则是 \(f(i) = (\lfloor \frac{i}{B} \rfloor, (i \times A^{-1}) \operatorname{mod} B)\)。可以发现 \(f\) 是双射,这说明我们的映射没什么问题。这么转化的好处就是,网格图上只存在向下的边(\(i \to i + B\)),向右/右下/最后一列到下一行第一列(注意不存在最后一列到本行第一列)的边(\(i \to i + A\))。
这样,就是一个 \(\lfloor \frac{i}{B} \rfloor \times B\) 的网格。我们状压轮廓线上每个点有没有边已经选了,然后枚举每一条边选不选进行转移。如果横着状压,就要记录 \(2^{B + 1}\) 的状态,复杂度 \(O(n2 ^ {B})\)。如果竖着状压,由于最后一列有向第一列的连边,所以还要时刻记录第一行的状态,这样就是 \(2 ^ {\lfloor \frac{i}{B} \rfloor} \times 2 ^ {\lfloor \frac{i}{B} \rfloor + 1}\),复杂度 \(O(n4^{\frac{n}{B}))\)。平衡一下,就是 \(O(n2^{\sqrt{2n}})\)。
写的时候偷了懒,注意到 \(B\) 很小的时候直接在原图上跑状压,状压前 \(B\) 个点选没选就好了。不用跑非常恶心难写细节多的轮廓线 dp。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 205, mod = 998244353;
int n, m, a, b;
inline void add(ll &x, ll y){ x += y; (x >= mod ? x -= mod : 0); }
namespace BSmall{ll f[2][1 << 20];vector<int> e[N][N];inline ll solve(int n, vector<int> e[]){int nw = 0, D = (1 << b) - 1;memset(f, 0, sizeof(f));f[0][0] = 1;for(int i = 0; i < n; ++i){nw ^= 1;memset(f[nw], 0, sizeof(f[nw]));for(int s = 0; s <= D; ++s){add(f[nw][(s << 1) & D], f[nw ^ 1][s]);}for(int s = 0; s <= D; ++s){for(int v : e[i]){if(!(s & (1 << i - v - 1)))add(f[nw][(((s | (1 << i - v - 1)) << 1) & D) | 1], f[nw ^ 1][s]);}}}ll ans = 0;for(int s = 0; s <= D; ++s) add(ans, f[nw][s]);return ans;}
}
namespace BLarge{ll f[2][1 << 10][1 << 11];int x[N], y[N], mp[N];vector<pii> e[N];bool g[N][N][3];// enumerate outinline ll solve(int n, int m){int D = (1 << n + 1) - 1, nw = 0;memset(f, 0, sizeof(f));auto clear = [&](int nw){for(int mask = 0; mask < (1 << n); ++mask){for(int s = 0; s <= D; ++s)f[nw][mask][s] = 0;}};f[nw ^ 1][0][0] = 1;// compute 0(we suppose the edges from column m - 1 is not considered)for(int i = 0; i < n - 1; ++i){nw ^= 1;clear(nw ^ 1);for(int s = 0; s < (1 << i + 1); ++s){add(f[nw ^ 1][s][0], f[nw][s][0]);if(!(s & (1 << i)) && g[i + 1][0][0]) //veradd(f[nw ^ 1][s | (1 << i) | (1 << i + 1)][0], f[nw][s][0]);}}nw ^= 1;clear(nw ^ 1);for(int s = 0; s < (1 << n); ++s){add(f[nw ^ 1][s][s << 1], f[nw][s][0]);if(!(s & 1) && g[0][1][1]) // horadd(f[nw ^ 1][s | 1][s << 1 | 1], f[nw][s][0]); } auto cvt = [&](int j){nw ^= 1;clear(nw ^ 1);for(int mask = 0; mask < (1 << n); ++mask){for(int s = 0; s <= D; ++s){// not choosingadd(f[nw ^ 1][mask][(s << 1) & D], f[nw][mask][s]);if(g[0][j + 1][1] && !(s & 1)) // horadd(f[nw ^ 1][mask][(s << 1) & D | 1], f[nw][mask][s]);}}};// compute 1for(int i = 0; i < n; ++i){nw ^= 1;clear(nw ^ 1);for(int mask = 0; mask < (1 << n); ++mask){for(int s = 0; s <= D; ++s){// not choosingif(s & (1 << i + 1)) add(f[nw ^ 1][mask][s ^ (1 << i + 1)], f[nw][mask][s]);else add(f[nw ^ 1][mask][s], f[nw][mask][s]); if(g[i + 1][1][0] && !(s & (1 << i))) // veradd(f[nw ^ 1][mask][s | (1 << i) | (1 << i + 1)], f[nw][mask][s]);if(g[i + 1][1][2] && !(s & (1 << i + 1))) // incadd(f[nw ^ 1][mask | (1 << i)][s | (1 << i + 1)], f[nw][mask][s]);if(g[i + 1][1][1] && !(s & (1 << i + 2))) // horadd(f[nw ^ 1][mask | (1 << i + 1)][s | (1 << i + 1) | (1 << i + 2)], f[nw][mask][s]);}}}cvt(1);// compute 2 ~ m - 1for(int j = 2; j < m; ++j){for(int i = 0; i < n; ++i){nw ^= 1;clear(nw ^ 1);for(int mask = 0; mask < (1 << n); ++mask){for(int s = 0; s <= D; ++s){// not choosingif(s & (1 << i + 1)) add(f[nw ^ 1][mask][s ^ (1 << i + 1)], f[nw][mask][s]);else add(f[nw ^ 1][mask][s], f[nw][mask][s]);if(g[i + 1][j][0] && !(s & (1 << i))) // veradd(f[nw ^ 1][mask][s | (1 << i) | (1 << i + 1)], f[nw][mask][s]);if(g[i + 1][j][2] && !(s & (1 << i + 1))) // incadd(f[nw ^ 1][mask][s | (1 << i + 1)], f[nw][mask][s]);if(g[i + 1][j][1] && !(s & (1 << i + 2))) // horadd(f[nw ^ 1][mask][s | (1 << i + 1) | (1 << i + 2)], f[nw][mask][s]);}}}if(j != m - 1) cvt(j);}ll ans = 0;for(int i = 0; i < n; ++i){nw ^= 1;clear(nw ^ 1);for(int mask = 0; mask < (1 << n); ++mask){for(int s = 0; s < (1 << n); ++s){add(f[nw ^ 1][mask][s], f[nw][mask][s]);if(g[i + 1][0][2] && !(mask & (1 << i + 1)) && !(s & (1 << i))) // incadd(f[nw ^ 1][mask | (1 << i + 1)][s | (1 << i)], f[nw][mask][s]);}}}nw ^= 1;for(int mask = 0; mask < (1 << n); ++mask){for(int s = 0; s < (1 << n); ++s){add(ans, f[nw][mask][s]);}}return ans;}
}
signed main(){cin.tie(nullptr)->sync_with_stdio(0);cin >> n >> m >> a >> b; if(a == b) b = n - 1;if(a > b) swap(a, b);int d = __gcd(a, b);a /= d, b /= d;if(b <= 20){using namespace BSmall;for(int i = 1; i <= m; ++i){int u, v; cin >> u >> v;u--, v--;e[v % d][v / d].emplace_back(u / d);}ll ans = 1;for(int i = 0; i < d; ++i){ans = (ans * solve((n + d - 1) / d, e[i])) % mod;}cout << ans;}else{using namespace BLarge;for(int i = 0; i < b; ++i) mp[i * a % b] = i; for(int i = 0; i < n; ++i) x[i] = i / b, y[i] = mp[i % b];for(int i = 1; i <= m; ++i){int u, v; cin >> u >> v;u--, v--;e[u % d].emplace_back(u / d, v / d);}ll ans = 1;for(int i = 0; i < d; ++i){memset(g, 0, sizeof(g));for(auto t : e[i]){int u, v; tie(u, v) = t;if(x[u] == x[v]) g[x[v]][y[v]][1] = 1;else if(y[u] == y[v]) g[x[v]][y[v]][0] = 1;else g[x[v]][y[v]][2] = 1;}ans = (ans * solve((((n + d - 1) / d) + b - 1) / b, b)) % mod;}cout << ans;}return 0;
}
Saummary
我记得这场 T2 没做出来,T3 因为写过一个边相交的版本用了它那个复杂度的拆链方法,但实际上完全没必要。人类智慧还是得提升啊。