rec.md
大概就是做一些省选的题吧,然后还剩下的东西大概就是会等到省选完再学吧。
省选2024 迷宫守卫
之前没有做过的,现在来补一下。
注意到第一个点是很重要的,先考虑第一个点选什么然后一个一个推下去,显然有朴素的 \(\rm DP\):\(f_{u, k}\) 表示以 \(u\) 节点的子树中,给了 \(k\) 块钱,可以让答案 \(\ge f_{u, k}\) 中的 \(f_{u, k}\) 的最大值。
转移显然 \(f_{u, j} = f_{ls, j} + \min(f_{rs, j}, w_u)\),初始化 \(f_{u, 0\dots q_u} = 0, f_{u, q_u + 1} = +\infty\),然后由于一层中第二维的总和是 \(2^n\),维护拐点即可做到 \(O(2^n n^2) / O(2^n n)\)(使用归并排序)。
关键是如何求答案。考虑定义 \(sol(u, k)\) 为考虑 \(u\) 子树内最优的排列,且给了 \(k\) 块钱,花费的钱数。
考虑 \(f_{u, j} \le k\) 的最大 \(j\),那么下一个走到的数一定是 \(j\)。找到 \(j\) 所在的子树。
-
若 \(j\) 在右子树:那么 \(w_u\) 一定不会选,由于是先访问右子树,那么需要尽量给右子树更多的钱,于是我们预留左子树需要的钱数 \(f_{ls, j}\),执行 \(cst = sol(rs, k - f_{ls, j}), sol(ls, k - cst)\)。
-
若 \(j\) 在左子树:考虑 \(w_u\) 是否会被选择,首先执行 \(cst = sol(ls, k - \min(f_{rs, j}, w_u))\)。由于选择 \(w_u\) 会导致 \(\rm Alice\) 可以直接使用的钱变少,肯定是更劣的,于是我们能不选就不选,即 \(f_{u, rs} \ge k - cst\) 时,我们才会选择 \(w_u\)。然后执行 \(sol(rs, k - cst - w_{u} / 0)\) 即可。
这样时间复杂度就是 \(O(2^n n)\),码量不大,大概 \(1h + \delta\) 写+调。
qwq
#include<bits/stdc++.h>
#define ll long long
//#define int long long
#define pir pair<int, ll>
#define pb emplace_back
#define ls (u << 1)
#define rs (u << 1 | 1)
#define lf(x) ((x >= (1ll << n)))
#define fi first
#define se second
using namespace std;
const int N = (1ll << 17) + 10;
const ll INF = 1e18;
int n;
ll w[N], q[N], rk[N];
vector<pir> vec[N];
inline void dp(int u){
if(lf(u)){vec[u].pb(make_pair(q[u], 0ll)); return;}
dp(ls); dp(rs); int siz = vec[ls].size() - 1; //vec[ls].pb(pINF); vec[rs].pb(pINF);
int i = siz, j = siz;
while(i >= 0 && j >= 0){
int val = 0;
if(vec[ls][i].fi > vec[rs][j].fi) val = vec[ls][i].fi, i--;
else val = vec[rs][j].fi, j--;
vec[u].pb(make_pair(val, ((i != siz) ? vec[ls][i + 1].se : INF) + min(w[u], ((j != siz) ? vec[rs][j + 1].se : INF))));
}
while(i >= 0) vec[u].pb(make_pair(vec[ls][i].fi, vec[ls][i].se + min(vec[rs][0].se, w[u]))), i--;
while(j >= 0) vec[u].pb(make_pair(vec[rs][j].fi, vec[ls][0].se + min(vec[rs][j].se, w[u]))), j--;
reverse(vec[u].begin(), vec[u].end());
// cerr << u << "\n";
// for(auto qwq : vec[u]) cerr << qwq.fi << " " << qwq.se << "\n";
}
inline int findpos(int u, ll val){
int ret = 0, L = 0, R = vec[u].size() - 1;
while(L <= R){
int mid = (L + R >> 1);
if(vec[u][mid].se <= val) L = mid + 1, ret = vec[u][mid].fi;
else R = mid - 1;
}
return ret;
}
inline ll getf(int u, int j){
ll ret = 0, L = 0, R = vec[u].size() - 1;
while(L <= R){
int mid = (L + R >> 1);
if(vec[u][mid].fi >= j) ret = vec[u][mid].se, R = mid - 1;
else L = mid + 1;
}
if(L == vec[u].size() && (!ret)) ret = INF;
// cerr << u << " " << j << " " << ret << "\n";
return ret;
}
inline ll sol(int u, ll k, int dep){
// cerr << u << " " << k << "\n";
if(lf(u)){cout << q[u] << " "; return 0;}
int j = findpos(u, k); ll ret = 0;
// cerr << u << " " << j << "\n";
if(rk[j] & (1ll << (n - dep - 1))){
ret = sol(rs, k - getf(ls, j), dep + 1);
ret += sol(ls, k - ret, dep + 1);
} else {
ll fr = getf(rs, j);
ret += sol(ls, k - min(fr, w[u]), dep + 1);
if(fr > k - ret) ret += w[u];
ret += sol(rs, k - ret, dep + 1);
} return ret;
}
inline void solve(){
ll k; cin >> n >> k;
for(int i = 1; i < (1ll << n); i++) cin >> w[i];
for(int i = (1ll << n); i < (1ll << (n + 1)); i++) cin >> q[i], rk[q[i]] = i;
dp(1); sol(1, k, 0); cout << "\n";
}
inline void clr(){
for(int i = 1; i < (1ll << (n + 1)); i++) vec[i].clear();
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int T; cin >> T; while(T--) clr(), solve();
return 0;
}
ABC245Ex Product Modulo 2
\(\rm Trick\):根据 \(\rm CRT\) 的结论,假设确定了每个质数维度上的余数,那么就可以唯一确定原模数的取值。于是我们分开每一类质数考虑,即考虑 \(m = p^k\) 的情况。
经典的,我们先不考虑最后一位的取值,而是考虑前 \(n - 1\) 位最后的乘积在 \(p\) 这一维上的表现。具体的,设 \(f_{i, j}\) 是前 \(i\) 位,乘积中恰好只含 \(p^j\),即可以表示为 \(x \times p^j\)(\(\gcd(x, p) = 1\)),于是有转移 \(f_{i, j} \times ccb_k \to f_{i + 1, \min(mx, j + k)}\)。其中 \(ccb_i\) 是 \(0 \dots m - 1\) 中恰好含 \(p^i\) 项的数的个数,容易转 \(\ge\) 解决。不难发现每一轮的转移都是一致的,于是可以矩阵快速幂做到 \(O(\log^3 m \log n)\)。
然后考虑假设前 \(i\) 位的乘积可以表示成 \(x_1 \times p^{y_1}\),最后的结果可以表示成 \(x_2 \times p^{y_2}\)。那么相当于要求最后一个数 \(l = x \times p^y\) 满足 \(y = y_2 - y_1\)。可以改写为:
\(x_1 \times x \equiv x_2 \pmod {p^{mx - y_1 - y}}\)
由于 \(\gcd(p, x_1) = 1\),于是 \(x \equiv x_2 \times x_1^{-1} \pmod {p^{mx}}\)。提取出来即 \(x = x_2 \times x_1^{-1} + wp^{mx - y_1 - y}\)。即求有多少个 \(w\) 满足条件,不难发现由于 \(x_1 \in [0, p^{mx - b})\),于是 \(w \in [0, p^a)\)。即最后的答案为 \(\sum_{i = 0}^{mx}{[K \bmod p^i = 0] f_{n - 1, i} \times p^i}\)。
注意 \(K \bmod p^{mx} = 0\) 的情况,直接乘上 \(f_{n, mx}\) 即可。
qwq
#include<bits/stdc++.h>
#define ll long long
//#define int long long
#define pir pair<int, int>
#define pb emplace_back
using namespace std;
const int N = 40;
const ll mod = 998244353;
ll n, m, A, ccb[N], pw[N];
inline void ADD(ll& x, ll y){x += y; (x >= mod) ? (x -= mod) : 0ll;}
struct mat{
ll a[N + 5][N + 5];
mat(){memset(a, 0, sizeof a);}
inline void I(){
for(int i = 0; i < N; i++) a[i][i] = 1;
}
};
inline mat operator*(struct mat A, struct mat B){
mat ret;
for(int k = 0; k < N; k++)
for(int i = 0; i < N; i++)
for(int j = 0; j < N; j++) ADD(ret.a[i][j], A.a[i][k] * B.a[k][j] % mod);
return ret;
}
inline mat mpow(mat bas, ll y){
mat ans; ans.I();
for(; y; y >>= 1, bas = bas * bas) if(y & 1) ans = ans * bas;
return ans;
}
inline ll solvesi(ll p, int mx){
//cout << p << " " << mx << "\n";
ll ans = 0; mat bas; pw[0] = 1;
for(int i = 1; i <= mx; i++) pw[i] = pw[i - 1] * p;
for(int i = 0; i < mx; i++) ccb[i] = (pw[mx - i] - pw[mx - i - 1]) % mod;
ccb[mx] = 1;
for(int i = 0; i <= mx; i++){
for(int j = 0; j <= mx; j++) ADD(bas.a[i][min(mx, i + j)], ccb[j]);
}
mat ret; ret.a[0][0] = 1;
if(A % pw[mx] == 0){
ret = ret * mpow(bas, n);
// cout << ret.a[0][mx] << "\n";
return ret.a[0][mx];
}
ret = ret * mpow(bas, n - 1);
for(int i = 0; i <= mx; i++){
if(A % pw[i] != 0) break;
ADD(ans, ret.a[0][i] * pw[i] % mod);
}
return ans;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin >> n >> A >> m; ll ans = 1;
for(ll i = 2; i * i <= m; i++){
if(m % i == 0){
int cnt = 0;
while(m % i == 0) m /= i, cnt++;
ans = ans * solvesi(i, cnt) % mod;
}
} if(m > 1) ans = ans * solvesi(m, 1) % mod;
cout << ans;
return 0;
}

浙公网安备 33010602011771号