Solution Set -「LOCAL」冲刺省选 Round XII
\(\mathscr{Summary}\)
和出题人很有缘分但是没有珍惜.jpg
A 题有一个显然的二维偏序斜率式,以及显然的 CDQ 套李超树 \(\mathcal O(n\log^2n)\) 做法,写出来跑的飞快就不管了,算是签到。
B 题,大家的正解做法和标算的做法我都想过,越写越萎最后成了暴力 qwq。
C 题我只能躬逢胜饯了,至少写了暴力。(
\(\mathscr{Solution}\)
\(\mathscr{A}-\) Array
给定序列 \(\{a_n\}\) 和常数 \(C\),将 \(\{a_n\}\) 任意分割为若干区间,设得到 \(k\) 个区间,第 \(i\) 个的最大值为 \(b_i\),最大化
\(1\le n,a_i\le10^6\)。
令 \(f(i)\) 表示 \(i\) 结尾,\(a_i\) 是最后一段最大值时,前缀最大和。记 \(l_i,r_i\) 分别表示 \(a_i\) 能 cover 的区间左右端点,那么
一看 \(\max\) 底下是个二维偏序,直接 CDQ + 李超树,大概是李超树常数巨小所以飞快。
“最大化最大 <=> 最大化任意”,发现 \(r_j+1\ge l_i\) 直接扔掉不会造成最优决策,李超树模拟一遍 \(\mathcal O(n\log n)\)。
标算貌似是笛卡尔树上启发式合并凸包,也是 \(\mathcal O(n\log n)\),不知道会不会有人想写。(
\(\mathscr{B}-\) Difference
交互题。你需要确定一个长度为 \(n\) 的序列 \(\{a_n\}\),给定两种函数接口:
int qry1(int k)
交互库返回 \(a_k\) 的值,使用次数不超过 \(2\) 次;std::vector<int> qry2(std::vector<int> S)
交互库以任意顺序返回可重集 \(\{|a_i-a_j|\mid i,j\in S,i<j\}\) 的所有元素,使用次数不超过 \(30\) 次。
\(n\le250\),\(0\le a_i\le10^9\),\(\{a_n\}\) 的元素互不相同。
法一 分两步:先确定最大或最小值位置,然后通过与该位置的差值确定每个元素。
第一步,二分查询序列前缀,判断前缀极差是否等于全局极差,最后得到的位置 \(p\) 满足 \(a_p\) 是最大值或最小值。
第二步,注意到 \(a\) 互不相同,所以我们二进制分组,对于 bit \(b\),询问得到 \(S_b=\{|a_i-a_p|\mid b\in i\}\),那么对于一个 \(i\),\(|a_p-a_i|\) 必然存在且仅存在与所有 \(i\) 的 1 bit 位置,借此可以反推出 \(a_i\) 的值。最后确定一下最大值最小值各自的位置就好。
对于不那么 corner 的 \(n\),询问 1 使用 \(2\) 次,询问 2 使用约 \(3\log n\) 次。
法二 并行分治,我只想到分治。(
先随便确定两个具体的值,之后可以三步询问一组问到一个下标集合内的元素集合。如果把一个值集合正确划分到两块下标集合就能实现分治。注意到每层分治的所有划分可以并行询问,即每层只需要 \(3\) 次,总共也是 \(3\log n\) 次。
\(\mathscr{C}-\) Randomxor
《本题是一题坐拥十一个 tags 的好题》。
给定 \(\{a_n\},\{b_m\},L\),求序列 \(\{p_L\}\in\{1..n\}^L\) 的数量,满足
答案对 \(998244353\) 取模,题目描述中的所有下标从 \(1\) 开始。
\(n\le35\),\(m\le20\),\(L\le4\times10^3\),\(0\le a_i,b_i\le10^9\)。
题解里所有下标从 \(0\) 开始。
考虑一个暴力容斥 DP。令 \(f(i,j,k)\) 表示用 \(k\) 个数把 \(b_i\) 异或成 \(b_j\) 的方案数,\(g(i,j)\) 表示前 \(i\) 个数,在 \(i\) 位置异或和撞到 \(b_j\) 的带容斥系数方案数。那么
当然这个转移本身会 T,求 \(f\) 也很困难。我们需要分别进行优化。
对于 \(f\),取 \(\{a_n\}\) 张成的空间 \(\mathcal A\) 的基 \(\mathscr B\),首先排除掉 \(b_i\notin\mathcal A\),此后对 \(|\mathscr B|\) 的取值进行复杂度平衡。设计以下两种求 \(f\) 的方法:
第一种,构造一个 \(\varphi:\mathcal A\rightarrow \{0,1\}^{|\mathscr B|}\) 作为 \(\mathcal A\) 与 \(\{0,1\}^{|\mathscr B|}\) 的同构映射,计算二元 GF \(h(x,y)\):
即对 \((1+x^{\varphi(a_i)y})\) 做关于 \(x\) 的异或卷积,FWT 可以手算,IFWT 还是得显式实现。得到的 \([x^uy^v]h(x,y)\) 就表示用 \(v\) 个不重复的数异或得到 \(\varphi^{-1}(u)\) 的方案数。这一部分复杂度为 \(\mathcal O(n^22^{|\mathscr B|})\)。
第二种,枚举 \(b_i,b_j\),并枚举 \(\{a_i\mid a_i\notin\mathscr B\}\) 的子集 \(S\),想要异或出 \(b_i\oplus b_j\),就需要在 \(\mathscr B\) 中取出 \(b_i\oplus b_j\oplus\left(\bigoplus_{s\in S} s\right)\)。分别预处理 \(\mathscr B\) 线性组合出 \(s\),线性组合出 \(b_i\oplus b_j\) 所需的向量集合,两集合的异或就是线性组合出 \(b_i\oplus b_j\oplus\left(\bigoplus_{s\in S} s\right)\) 所需的向量集合,继而也能求到用 \(v\) 个不同的数异或得到 \(u\) 的方案数。这一部分的复杂度为 \(\mathcal O(m^22^{n-|\mathscr B|})\)。
两种方法最后都需要解决一个组合问题:\(n\) 种颜色的球,其中确定的 \(k\) 种颜色只能放奇数个,其余 \(n-k\) 种颜色只能放偶数个,最终放满 \(l\) 个有序位置的方案数。那么方案数为
预处理出
就能完成求到的结果到 \(f\) 的转换。精细实现可以做到 \(\mathcal O(n^2L)\),再算上转换的复杂度 \(\mathcal O(m^2nL)\),平衡两种算法,求 \(f\) 的复杂度可以做到 \(\mathcal O(\min\{n^22^{|\mathscr B|},m^22^{n-|\mathscr B|}\}+m^2nL+n^2L)\)。
此后,考虑 \(g\) 的转移,发现转移是第一维上的卷积,所以想到分治 FFT 求解。注意到一个性质:\(g(i,\star)\) 向 \(g(j,\star)\) 的转移系数不与 \(i,j\) 直接相关,而是与 \(j-i\) 相关,故可以尝试分治的同时记录左侧分治区间内部贡献的线性变换之复合,在完成左侧对右侧贡献后直接将这一复合作用在右侧而不用递归右侧——把分治换成倍增。
具体地,令多项式矩阵 \(\newcommand{\vct}[1]{\boldsymbol{#1}} G_l=\{\vct{g}_{m\times m}\}\),其中 \(\vct{g}_{ij}\) 是一个 \(l-1\) 次多项式,或说是一个 \(l\) 维向量,\(\vct g_{ij}^{(k)}\) 就表示从 \(b_i\) 出发,填 \(k\) 个数,最终撞到 \(b_j\) 的带容斥系数方案数。至于 \(i\) 这维存在的必要性,将在稍后的过程中体现。特别地,我们为 \(b\) 增加一个元素 \(b_m=0\),并在 \(f\) 中钦定它只能向外转移,不能由其他位置转移得到,这样它就能作为我们整个序列的出发点。
初始时,自然有 \(G_1=I\),设现在已知 \(G_l\),考虑如何“倍增”求到 \(G_{2l}\)。
还是从分治乘法的角度,先考虑 \(\vct{g}_{l,ij}^{(0..l-1)}\) 对 \(\vct g_{2l,ij}^{(l..2l-1)}\) 的贡献。取转移矩阵 \(F=\{\vct f_{m\times m}\}\),其中 \(\deg \vct f_{ij}=2l\),\(\vct f_{ij}^{(k)}=f(i,j,k)\)。那么矩阵
即是左对右贡献之后右侧的初始状态(其中元素乘积是多项式卷积,仅取乘积矩阵每个元素的 \(l..2l-1\) 次项)。我们还需要考虑右侧的内部转移。注意到 \(G_1=I\),那么 \(G_l\) 就是所谓“左侧线性变化的复合”,直接施加到右侧的初始状态上,得到
最后的优化,先对所有多项式 DFT 得到点值,矩阵乘法中元素乘积变为向量点积。这一部分的复杂度是 \(T(L)=\mathcal O(m^3L+m^2L\log L)+T(L/2)=\mathcal O(m^3L+m^2L\log L)\)。
大概会有一个矩阵求逆求 \(G\) 的算法,我能感受但不能乱讲,看了 crashed 的题解再说 awa。
我猜群众呼声比较大,所以放一下代码叭~
\(\mathscr{Code}\)
/*+Rainybunny+*/
#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l, rep##i = r; i <= rep##i; ++i)
#define per(i, r, l) for (int i = r, per##i = l; i >= per##i; --i)
const int MAXN = 35, MAXM = 20, W = 29, MAXL = 1 << 14;
const int MOD = 998244353, INV2 = MOD + 1 >> 1;
int n, m, L, a[MAXN + 5], b[MAXM + 5];
int fac[MAXN + 5], ifac[MAXN + 5], npwr[MAXL + 5];
int trs[MAXM + 5][MAXM + 5][MAXL + 5];
namespace Basic {
inline int mul(const int u, const int v) { return 1ll * u * v % MOD; }
inline void subeq(int& u, const int v) { (u -= v) < 0 && (u += MOD); }
inline int sub(int u, const int v) { return (u -= v) < 0 ? u + MOD : u; }
inline void addeq(int& u, const int v) { (u += v) >= MOD && (u -= MOD); }
inline int add(int u, const int v) { return (u += v) < MOD ? u : u - MOD; }
inline int mpow(int u, int v) {
int ret = 1;
for (; v; u = mul(u, u), v >>= 1) ret = mul(ret, v & 1 ? u : 1);
return ret;
}
namespace PolyOper {
const int G = 3;
int omega[15][MAXL + 5];
inline void init() {
rep (i, 1, 14) {
int* wi = omega[i];
wi[0] = 1, wi[1] = mpow(G, MOD - 1 >> i);
rep (j, 2, (1 << i) - 1) wi[j] = mul(wi[j - 1], wi[1]);
}
}
inline void ntt(const int n, int* u, const int tp) {
static int rev[MAXL + 5], las = -1;
if (las != n) {
las = n;
rep (i, 0, n - 1) rev[i] = rev[i >> 1] >> 1 | (i & 1) * n >> 1;
}
rep (i, 0, n - 1) if (i < rev[i]) std::swap(u[i], u[rev[i]]);
for (int i = 1, stp = 1; stp < n; ++i, stp <<= 1) {
int* wi = omega[i];
for (int j = 0; j < n; j += stp << 1) {
rep (k, j, j + stp - 1) {
int ev = u[k], ov = mul(wi[k - j], u[k + stp]);
u[k] = add(ev, ov), u[k + stp] = sub(ev, ov);
}
}
}
if (!~tp) {
int inv = mpow(n, MOD - 2);
std::reverse(u + 1, u + n);
rep (i, 0, n - 1) u[i] = mul(u[i], inv);
}
}
} // using namespace PolyOper;
inline void init() {
PolyOper::init();
fac[0] = 1;
rep (i, 1, n) fac[i] = mul(i, fac[i - 1]);
ifac[n] = mpow(fac[n], MOD - 2);
per (i, n - 1, 0) ifac[i] = mul(i + 1, ifac[i + 1]);
npwr[0] = 1;
rep (i, 1, L) npwr[i] = mul(npwr[i - 1], n);
}
inline int bino(const int u, const int v) {
return v < 0 || u < v ? 0 : mul(fac[u], mul(ifac[v], ifac[u - v]));
}
} using namespace Basic;
namespace TransferInit {
int bas[W + 5], ref[W + 5], num[W + 5], dim; // linear basis.
int eprd[MAXN + 5][MAXL + 5];
bool inb[MAXN + 5];
inline void initExp() {
int ipwr = mpow(2, MOD - 1 - n);
rep (a, 0, n) { // (e^x+e^{-x})^a * (...)^{n-a}.
rep (i, -n, n) {
int coe = 0;
rep (j, -a, a) {
if (int u = a - j, v = n - a + j - i;
!(u % 2) && !(v % 2) && u >= 0 && v >= 0) {
u >>= 1, v >>= 1;
(u & 1 ? subeq : addeq)(coe,
mul(bino(a, u), bino(n - a, v)));
}
}
coe = mul(coe, ipwr);
for (int j = 0, pwri = 1; j <= L; ++j, pwri = mul(pwri, i + MOD)) {
addeq(eprd[a][j], mul(coe, pwri));
}
}
// rep (i, 0, L) printf("%d ", eprd[a][i]);
// puts("");
}
}
namespace SmallSpace {
const int DIM = 17;
int dc[MAXN + 5], res[MAXN + 5][1 << DIM];
inline void ifwt() {
rep (z, 0, n) {
int* uz = res[z];
for (int stp = 1; stp < 1 << dim; stp <<= 1) {
for (int i = 0; i < 1 << dim; i += stp << 1) {
rep (j, i, i + stp - 1) {
int t = uz[j];
uz[j] = mul(add(t, uz[j + stp]), INV2);
uz[j + stp] = mul(sub(t, uz[j + stp]), INV2);
}
}
}
}
}
inline void work() {
int cnt = 0;
rep (i, 0, W) num[i] = bas[i] ? cnt++ : -1;
rep (i, 0, n - 1) {
int x = a[i], &d = SmallSpace::dc[i];
per (j, W, 0) if (x >> j & 1) d |= 1 << num[j], x ^= bas[j];
}
rep (i, 0, (1 << dim) - 1) res[0][i] = 1;
rep (i, 0, n - 1) {
per (z, i + 1, 1) {
rep (j, 0, (1 << dim) - 1) {
(__builtin_parity(j & dc[i]) ? subeq : addeq)
(res[z][j], res[z - 1][j]);
}
}
}
ifwt();
rep (i, 0, m - 1) rep (j, i, m - 1) {
int v = b[i] ^ b[j], d = 0;
per (k, W, 0) if (v >> k & 1) {
assert(bas[k]), v ^= bas[k], d |= 1 << num[k];
}
rep (o, 0, n) {
rep (k, 1, L) {
addeq(trs[i][j][k], mul(res[o][d], eprd[o][k]));
}
}
rep (k, 1, L) trs[j][i][k] = trs[i][j][k];
}
}
} // namespace TransferInit::SmallSpace.
namespace BigSpace {
const int RES = 18;
int useM[MAXM + 5][MAXM + 5];
inline void work() {
std::vector<int> outb;
rep (i, 0, n - 1) if (!inb[i]) outb.push_back(i);
rep (i, 0, m - 1) rep (j, i, m - 1) {
int x = b[i] ^ b[j], &use = useM[i][j] = 0;
per (k, W, 0) if (x >> k & 1) {
assert(bas[k]), x ^= bas[k], use ^= ref[k];
}
}
rep (S, 0, (1 << outb.size()) - 1) {
int s = 0;
rep (i, 0, int(outb.size()) - 1) if (S >> i & 1) s ^= a[outb[i]];
int useS = 0;
per (k, W, 0) if (s >> k & 1) {
assert(bas[k]), s ^= bas[k], useS ^= ref[k];
}
rep (i, 0, m - 1) rep (j, i, m - 1) {
++trs[i][j][__builtin_popcount(S)
+ __builtin_popcount(useS ^ useM[i][j])]; // save it temporarily.
}
}
rep (i, 0, m - 1) rep (j, i, m - 1) {
static int buc[MAXN + 5]; int* cur = trs[i][j];
rep (k, 0, n) buc[k] = cur[k], cur[k] = 0;
rep (o, 0, n) if (buc[o]) {
rep (k, 1, L) {
addeq(cur[k], mul(buc[o], eprd[o][k]));
}
}
rep (k, 1, L) trs[j][i][k] = trs[i][j][k];
}
}
} // namespace TransferInit::BigSpace.
inline void work() {
initExp();
rep (i, 0, n - 1) {
int x = a[i], r = 0;
per (j, W, 0) if (x >> j & 1) {
if (bas[j]) x ^= bas[j], r ^= ref[j];
else {
++dim, bas[j] = x, ref[j] = r | 1 << j, inb[i] = true;
break;
}
}
}
int tmp = m; m = 0;
rep (i, 0, tmp - 1) {
int x = b[i], flg = 1;
per (j, W, 0) if (x >> j & 1) {
if (!bas[j]) { flg = false; break; }
x ^= bas[j];
}
if (flg) b[m++] = b[i];
}
b[m++] = 0;
if (dim <= n >> 1) {
fprintf(stderr, "calling SS::work().\n");
SmallSpace::work();
} else {
fprintf(stderr, "calling BS::work().\n");
BigSpace::work();
}
}
} // namespace TransferInit.
namespace Solution {
/*
G: final answer;
A: left->right temp.
C: current transfer matrix;
T: past transfer sum (aka last G).
R: left->right temp.
*/
int G[MAXM + 2][MAXM + 2][MAXL + 5];
int A[MAXM + 2][MAXM + 2][MAXL + 5], C[MAXM + 2][MAXM + 2][MAXL + 5];
int T[MAXM + 2][MAXM + 2][MAXL + 5], R[MAXM + 2][MAXM + 2][MAXL + 5];
inline void work() {
rep (i, 0, m - 1) rep (j, 0, m - 1) G[i][j][0] = T[i][j][0] = i == j;
rep (i, 0, m - 1) rep (j, 0, L) trs[i][m - 1][j] = 0;
int len = 1;
while (len <= L) {
int fft = len << 2;
// prepare
rep (i, 0, m - 1) rep (j, 0, m - 1) {
rep (z, 0, fft) A[i][j][z] = R[i][j][z] = 0;
rep (z, 0, (len << 1) - 1) C[i][j][z] = sub(0, trs[i][j][z]);
PolyOper::ntt(fft, C[i][j], 1);
PolyOper::ntt(fft, T[i][j], 1);
}
// left -> right
rep (i, 0, m - 1) rep (k, 0, m - 1) rep (j, 0, m - 1) {
rep (z, 0, fft - 1) {
addeq(A[i][j][z], mul(T[i][k][z], C[k][j][z]));
}
}
// right -> right prepare
rep (i, 0, m - 1) rep (j, 0, m - 1) {
PolyOper::ntt(fft, A[i][j], -1);
rep (k, 0, len - 1) A[i][j][k] = 0;
rep (k, len << 1, fft - 1) A[i][j][k] = 0;
PolyOper::ntt(fft, A[i][j], 1);
}
// right -> right
rep (i, 0, m - 1) rep (k, 0, m - 1) rep (j, 0, m - 1) {
rep (z, 0, fft - 1) {
addeq(R[i][j][z], mul(A[i][k][z], T[k][j][z]));
}
}
// summary
rep (i, 0, m - 1) rep (j, 0, m - 1) {
PolyOper::ntt(fft, R[i][j], -1);
rep (z, 0, len - 1) T[i][j][z] = G[i][j][z];
rep (z, len, (len << 1) - 1) T[i][j][z] = G[i][j][z] = R[i][j][z];
rep (z, len << 1, fft - 1) T[i][j][z] = 0;
}
len <<= 1;
}
int ans = npwr[L];
rep (i, 0, m - 1) rep (j, 1, L) {
addeq(ans, mul(npwr[L - j], G[m - 1][i][j]));
}
printf("%d\n", ans);
}
} // namespace Solution.
int main() {
freopen("randomxor.in", "r", stdin);
freopen("randomxor.out", "w", stdout);
scanf("%d %d %d", &n, &m, &L);
rep (i, 0, n - 1) scanf("%d", &a[i]);
rep (i, 0, m - 1) scanf("%d", &b[i]);
init();
fprintf(stderr, "init done.\n");
TransferInit::work();
fprintf(stderr, "transfer init done.\n");
Solution::work();
return 0;
}