2025 福建省队集训
2025 福建省队集训
http://218.5.5.242:9021/d/FJOI2025/
Day 1 - 唐彬峪
琥峪枫
这是一道交互题。
交互库有有一个 \(2^h - 1\) 个点的满二叉树,每个点有一个权值。
给定 \(h\) ,一次询问可以给出 \(x, d (d \geq 1)\) ,交互库将返回树上与 \(x\) 距离为 \(d\) 的点的权值和,若不存在则返回 \(0\) 。
试用 \(\leq 2n + 3\) 次询问求出所有点的权值和,要求每个点不能被询问超过 \(4\) 次。
\(2 \leq h \leq 15\)
考虑询问所有 \((x, 1)\) ,则根会被算两次,叶子会被算一次,其余点会被算三次。
考虑求根和叶子的权值,首先可以用 \(\leq n - 1\) 次询问 \((x, h + 1)\) 得到前两层的三个点,然后对这三个点用 \(\leq 2\) 次询问 \((x, h)\) 找到根。记根为 \(r\) ,两个儿子为 \(a, b\) ,则叶子的权值和即为 \((r, h - 1)\) ,根的权值和即为 \((a, 1) + (b, 1) - (r, 2)\) 。
但此时根最坏会被问 \(5\) 次,将求叶子的权值和从 \((r, h - 1)\) 转化为 \((a, h - 2) + (b, h - 2)\) 即可,总询问次数 \(2n + 3\) 。
#include <bits/stdc++.h>
#include "tree.h"
typedef long long ll;
using namespace std;
const int N = 1 << 15;
ll solve(int tp, int h) {
int n = (1 << h) - 1;
vector<ll> f(n + 1);
for (int i = 1; i <= n; ++i)
f[i] = ask(i, 1);
vector<pair<int, ll> > vec;
for (int i = 1; i < n; ++i)
if (!ask(i, h + 1))
vec.emplace_back(i, ask(i, h));
if (vec.size() < 3)
vec.emplace_back(n, ask(n, h));
int rt = (vec[0].second ? (vec[1].second ? vec[2].first : vec[1].first) : vec[0].first);
vec.erase(find(vec.begin(), vec.end(), make_pair(rt, 0ll)));
return (accumulate(f.begin(), f.end(), 0ll) + (vec[0].second + vec[1].second) * 2 +
(f[vec[0].first] + f[vec[1].first] - ask(rt, 2)) / 2) / 3;
}
篱莘龙
给定 \(a_{1 \sim n}, b_{1 \sim n}\) ,称集合 \(S\) 合法,当且仅当不存在 \(x, y \in S\) 满足 \(x \neq y\) 且 \(a_x > b_y\) 。
对于 \(k = 1, 2, \cdots, n\) ,求 \(\{ 1, 2, \cdots k \}\) 最大的合法子集大小。
\(n \leq 10^6\) ,所有 \(a_i, b_i\) 互不相同
首先可以发现 \(a_i > b_i\) 的下标只能选一个,因此需要考虑最大化选取 \(a_i < b_i\) 的下标数量,在此基础上考虑是否能多选一个 \(a_i > b_i\) 的下标。
先考虑不存在 \(a_i > b_i\) 的情况,此时选出的集合中一定存在一个点 \(x\) ,满足所有 \(a\) 都 \(\leq x\) ,且所有 \(b\) 都 \(\geq x\) ,用线段树维护所有 \(x\) 的答案即可。
下面考虑能否再加入一个 \(a_i > b_i\) 的下标,能够选取的条件为区间 \([b_i, a_i]\) 是当前选取的所有区间 \([a_j, b_j]\) 的子集,因此中必然不存在其它区间的端点。
考虑对于 \(a_i > b_i\) 的区间,取 \(x = b_i\) 处统计贡献(这是因为若其有用,则说明 \([b_i, a_i]\) 答案都相等),考虑维护:
- 插入 \(a_i > b_i\) 的区间:需要判断是否有区间都包含它或与它不交,这可以用树状数组维护当前存在的值,每次查询 \([b_i, 2n]\) 中最小的存在的端点 \(> a\) 时即可插入。
- 插入 \(a_i < b_i\) 的区间:不断删去与其有交的 \(a_i > b_i\) 的区间,这可以用线段树维护。
时间复杂度 \(O(n \log n)\) 。
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
const int N = 2e6 + 7;
int tp, n;
namespace SMT {
int mx[N << 2], tag[N << 2];
inline int ls(int x) {
return x << 1;
}
inline int rs(int x) {
return x << 1 | 1;
}
inline void spread(int x, int k) {
mx[x] += k, tag[x] += k;
}
inline void pushdown(int x) {
if (tag[x])
spread(ls(x), tag[x]), spread(rs(x), tag[x]), tag[x] = 0;
}
void update(int x, int nl, int nr, int l, int r, int k) {
if (l <= nl && nr <= r) {
spread(x, k);
return;
}
pushdown(x);
int mid = (nl + nr) >> 1;
if (l <= mid)
update(ls(x), nl, mid, l, r, k);
if (r > mid)
update(rs(x), mid + 1, nr, l, r, k);
mx[x] = max(mx[ls(x)], mx[rs(x)]);
}
} // namespace SMT
namespace BIT {
int c[N];
inline void update(int x, int k) {
for (; x; x -= x & -x)
c[x] = min(c[x], k);
}
inline int query(int x) {
int res = inf;
for (; x <= n * 2; x += x & -x)
res = min(res, c[x]);
return res;
}
} // namespace BIT
namespace SGT {
pair<int, int> mx[N << 2];
inline int ls(int x) {
return x << 1;
}
inline int rs(int x) {
return x << 1 | 1;
}
void insert(int x, int nl, int nr, int p, int k) {
if (nl == nr) {
mx[x] = make_pair(k, p);
return;
}
int mid = (nl + nr) >> 1;
if (p <= mid)
insert(ls(x), nl, mid, p, k);
else
insert(rs(x), mid + 1, nr, p, k);
mx[x] = max(mx[ls(x)], mx[rs(x)]);
}
pair<int, int> query(int x, int nl, int nr, int l, int r) {
if (l <= nl && nr <= r)
return mx[x];
int mid = (nl + nr) >> 1;
if (r <= mid)
return query(ls(x), nl, mid, l, r);
else if (l > mid)
return query(rs(x), mid + 1, nr, l, r);
else
return max(query(ls(x), nl, mid, l, r), query(rs(x), mid + 1, nr, l, r));
}
} // namespace SGT
inline void insert(int x) {
BIT::update(x, x);
for (;;) {
auto res = SGT::query(1, 1, n * 2, 1, x);
if (res.first < x)
break;
SMT::update(1, 1, n * 2, res.second, res.second, -1), SGT::insert(1, 1, n * 2, res.second, 0);
}
}
signed main() {
scanf("%d%d", &tp, &n);
memset(BIT::c, inf, sizeof(int) * (n * 2));
for (int i = 1; i <= n; ++i) {
int a, b;
scanf("%d%d", &a, &b);
if (a < b)
SMT::update(1, 1, n * 2, a, b, 1), insert(a), insert(b);
else if (a < BIT::query(b))
SMT::update(1, 1, n * 2, b, b, 1), SGT::insert(1, 1, n * 2, b, a);
printf("%d\n", SMT::mx[1]);
}
return 0;
}
*棽荠郁
给定一棵树,边带边权,边权有正有负。
定义 \(f(p_{1 \sim n}) = \sum_{i = 1}^n \mathrm{dist}(p_i, p_{i \bmod n + 1})\) ,求所有排列的 \(f\) 的最大值。
\(n \leq 2 \times 10^5\)
Day 2 - 魏忠浩
Change
给定 \(a_{1 \sim n}\) ,可以进行若干次操作:选择区间 \([l, r]\) ,对于所有 \(i \in \mathbb{N}, x \in \{ 0, 1 \}, l + 3i + x \leq r\) ,令 \(a_{l + 3i + x}\) 加上 \((-1)^x\) 。
求使得 \(a\) 全 \(0\) 的最少操作次数,或报告无解。
\(n \leq 3 \times 10^4\) ,\(|a_i| \leq 10^{10}\)
考虑构造长度为 \(n + 2\) 的新序列 \(b_i = a_{i - 2} + a_{i - 1} + a_i\) ,这里不妨假设 \(i < 1\) 和 \(i > n\) 时 \(a_i = 0\) ,则一次操作相当于:
- \(l \equiv r - 1 \pmod{3}\) :此时原序列中 \(+1, -1\) 数量相等,有 \(b_l \to b_l + 1, b_{r + 2} \to b_{r + 2} - 1\) ,不难发现 \(l, r + 2\) 在 \(\bmod 3\) 意义下相等。
- \(l \equiv r \pmod{3}\) :此时最后多了一个 \(+1\) ,有 \(b_l \to b_l + 1, b_{r + 1} \to b_{r + 1} + 1, b_{r + 2} \to b_{r + 2} + 1\) ,不难发现 \(l, r + 1, r + 2\) 在 \(\bmod 3\) 意义下两两不同。
- \(l \equiv r - 2 \pmod{3}\) :可以规约到第一种情况进行等价操作,因此无需考虑。
不难发现操作二会改变 \(b\) 的总和,因此操作二的数量是固定的。
考虑从后往前贪心,枚举 \(i = n + 2 \to 1\) ,其中扫到 \(i\) 时已经将 \(b_j (j > i)\) 均消成 \(0\) ,操作一个位置时记录一下前面模 \(3\) 为 \(0, 1, 2\) 的位置需要操作的情况:
- \(b_i = 0\) :无需处理。
- \(b_i > 0\) :不断进行操作一将 \(b_i\) 消成 \(0\) 。
- \(b_i < 0\) :
- 若 \(b_{i - 1} < 0\) ,则不断用操作二(因为一次可以同时增加两个位置,显然优于操作一),直到 \(\max(b_i, b_{i - 1}) = 0\) 或操作二次数用完为止。
- 若仍然有 \(b_i < 0\) ,则用前面操作留下的左端点增加该位置,直至 \(b_i = 0\) 或残留的操作用完为止。
- 若仍然有 \(b_i < 0\) ,考虑将 \(b_{i - 1}\) 用操作一减到 \(b_i\) ,然后一起用操作二消成 \(0\) ,若操作二次数不够则无解。
时间复杂度线性。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e6 + 7;
ll a[N];
int T, n;
signed main() {
freopen("trans.in", "r", stdin);
freopen("trans.out", "w", stdout);
scanf("%d%d", &T, &n);
while (T--) {
ll sum = 0;
for (int i = 1; i <= n; ++i)
scanf("%lld", a + i), sum += a[i];
if (sum > 0) {
puts("-1");
continue;
}
ll ans = sum = -sum;
a[n + 2] = a[n + 1] = 0;
for (int i = n + 2; i; --i)
for (int j = 1; j < min(3, i); ++j)
a[i] += a[i - j];
ll cnt[3] = {0, 0, 0};
for (int i = n + 2; i; --i) {
if (a[i] > 0)
cnt[i % 3] += a[i], ans += a[i];
else if (a[i] < 0) {
if (i > 1 && a[i - 1] < 0) {
ll k = min(sum, min(-a[i - 1], -a[i]));
a[i] += k, a[i - 1] += k, cnt[(i + 1) % 3] += k, sum -= k;
}
if (a[i] < 0) {
ll k = min(-a[i], cnt[i % 3]);
cnt[i % 3] -= k, a[i] += k;
if (a[i] < 0) {
if (-a[i] > sum) {
ans = -1;
break;
}
ll k = -a[i];
cnt[(i + 1) % 3] += k, cnt[(i - 1) % 3] += k, ans += k, sum -= k;
}
}
}
}
printf("%lld\n", ans);
}
return 0;
}
*Toxic
source:QOJ8592. Toxic Gene 2
这是一道交互题。
有 \(n = 1000\) 种细菌,标号为 \(0 \sim n - 1\) 。其中有若干细菌有毒,其余细菌正常,保证至少存在一个有毒细菌与至少一个正常细菌。
一次询问可以给出一排 \(\leq 1000\) 个细菌,每轮若一个正常细菌相邻处存在有毒细菌,则该细菌该轮过后会消失,交互库会返回每一轮分别存活的细菌数量,直至数量不变为止。
试用 \(\leq 21\) 次询问确定每个细菌是否有毒。
*Tree
给定一棵树,每个点有一个人。每个人会向上走直到根,且对于每个点 \(u\) ,当且仅当子树内的点都到达 \(u\) 时这些人才会继续向上走。
每个点处有一个二元组 \(P_i = (a_i, b_i)\) ,两个人第一次到达 \(u\) 时会同时获得 \(P_u\) ,每个人可能拥有多个相同的二元组。
定义树的价值为每个人获得的二元组集合中非严格偏序的二元组对的数量,其中 \(P_x, P_y\) 非严格偏序当且仅当 \([a_x > a_y] \neq [b_x > b_y]\) 。
求出整棵树的价值后,还需回答 \(q\) 次询问,每次删去一个或两个人后求整棵树的价值,询问之间相互独立。
\(n, q \leq 1.5 \times 10^5\) ,\(a_i\) 互不相同,\(b_i\) 互不相同,ML = 256MB
Day 3 - 黄佳旭
沙湾
有 \(n + m\) 个石子,颜色有 \(c + 1\) 种。其中有 \(m\) 个特殊石子,颜色为 \(c + 1\) 。对于剩下的 \(n\) 个石子,第 \(i\) 种颜色的石子有 \(a_i\) 个,保证 \(\sum_{i = 1}^n a_i = n\) 。
将石子排成一排,定义排放方案的价值为:找到左边第一个特殊石子,记其为从左到右第 \(x\) 个石子,然后考虑最右边的石子:
- 若为特殊石子,则价值为 \(w_x\) 。
- 否则记后缀极长同色段的长度为 \(y\) ,颜色为 \(u\) ,则价值为 \(w_x \times v_{u, y}\) 。
其中 \(w, v\) 由题目给定。
求所有方案的价值和,两个方案不同当且仅当存在一个位置颜色不同。
\(n \leq 2 \times 10^5\) ,\(m \leq 30\)
先考虑最右端为特殊石子的情况,枚举左边第一个特殊石子直接算即可做到 \(O(n)\) 。
然后考虑最右边为普通石子的情况。由于同色段恰好长 \(l\) 的方案数等于至少为 \(l\) 的方案数减去至少为 \(l + 1\) 的方案数,因此先将 \(v_{i, j}\) 差分,这样下面只要钦定长度至少为 \(l\) 即可。
枚举最右端的颜色和极长同色段长度,再枚举左边第一个特殊石子,记 \(M = \frac{n!}{\sum_{i = 1}^n a_i!}\) ,答案即为:
拆开组合数后发现 \(k\) 这一维能预处理卷积,于是直接 NTT 即可做到 \(O(m + n \log n)\) ,也可以加法公式拆开组合数后递推做到 \(O(nm)\) 。
#include <bits/stdc++.h>
using namespace std;
const int Mod = 998244353, rt = 3, invrt = (Mod + 1) / 3;
const int N = 4e5 + 7;
vector<int> v[N];
int w[N], a[N], fac[N], inv[N], invfac[N], f[N];
int n, m, c, all;
inline int add(int x, int y) {
x += y;
if (x >= Mod)
x -= Mod;
return x;
}
inline int dec(int x, int y) {
x -= y;
if (x < 0)
x += Mod;
return x;
}
inline int mi(int a, int b) {
int res = 1;
for (; b; b >>= 1, a = 1ll * a * a % Mod)
if (b & 1)
res = 1ll * res * a % Mod;
return res;
}
inline void prework(int n) {
fac[0] = fac[1] = 1;
inv[0] = inv[1] = 1;
invfac[0] = invfac[1] = 1;
for (int i = 2; i <= n; ++i) {
fac[i] = 1ll * fac[i - 1] * i % Mod;
inv[i] = 1ll * (Mod - Mod / i) * inv[Mod % i] % Mod;
invfac[i] = 1ll * invfac[i - 1] * inv[i] % Mod;
}
}
inline int C(int n, int m) {
return m > n || m < 0 ? 0 : 1ll * fac[n] * invfac[m] % Mod * invfac[n - m] % Mod;
}
namespace Poly {
#define cpy(f, g, n) memcpy(f, g, sizeof(int) * (n))
#define clr(f, n) memset(f, 0, sizeof(int) * (n))
const int N = 1e6 + 7;
int rev[N];
inline int calc(int n) {
int len = 1;
while (len < n)
len <<= 1;
for (int i = 0; i < len; ++i)
rev[i] = (rev[i >> 1] >> 1) | (i & 1 ? len >> 1 : 0);
return len;
}
inline void NTT(int *f, int n, int op) {
for (int i = 0; i < n; ++i)
if (i < rev[i])
swap(f[i], f[rev[i]]);
for (int k = 1; k < n; k <<= 1) {
int tG = mi(op == 1 ? rt : invrt, (Mod - 1) / (k << 1));
for (int i = 0; i < n; i += k << 1) {
int buf = 1;
for (int j = 0; j < k; ++j) {
int fl = f[i + j], fr = 1ll * buf * f[i + j + k] % Mod;
f[i + j] = add(fl, fr), f[i + j + k] = dec(fl, fr), buf = 1ll * buf * tG % Mod;
}
}
}
if (op == -1) {
int invn = mi(n, Mod - 2);
for (int i = 0; i < n; ++i)
f[i] = 1ll * f[i] * invn % Mod;
}
}
inline void Mul(int *f, int n, int *g, int m, int *res) {
static int a[N], b[N];
int len = calc(n + m - 1);
cpy(a, f, n), clr(a + n, len - n);
cpy(b, g, m), clr(b + m, len - m);
NTT(a, len, 1), NTT(b, len, 1);
for (int i = 0; i < len; ++i)
a[i] = 1ll * a[i] * b[i] % Mod;
NTT(a, len, -1), cpy(res, a, n + m - 1);
}
#undef cpy
#undef clr
} // namespace Poly
inline int calc1() {
if (m == 1)
return 1ll * w[n + 1] * all % Mod;
int ans = 0;
for (int i = 1; i <= n + 1; ++i)
ans = add(ans, 1ll * w[i] * all % Mod * C(n + m - i - 1, m - 2) % Mod);
return ans;
}
inline int calc2() {
int ans = 0;
for (int i = 0; i < c; ++i)
for (int j = 1; j <= a[i]; ++j)
ans = add(ans, 1ll * f[n - j + 1] * v[i][j] % Mod * invfac[m - 1] % Mod *
all % Mod * invfac[n] % Mod * fac[n - j] % Mod * fac[a[i]] % Mod * invfac[a[i] - j] % Mod);
return ans;
}
signed main() {
freopen("sand.in", "r", stdin);
freopen("sand.out", "w", stdout);
scanf("%d%d%d", &n, &m, &c);
for (int i = 1; i <= n + 1; ++i)
scanf("%d", w + i);
prework(n + m), all = fac[n];
for (int i = 0; i < c; ++i)
scanf("%d", a + i), all = 1ll * all * invfac[a[i]] % Mod;
for (int i = 0; i < c; ++i) {
v[i].resize(a[i] + 1);
for (int j = 1; j <= a[i]; ++j)
scanf("%d", &v[i][j]);
for (int j = a[i]; j > 1; --j)
v[i][j] = dec(v[i][j], v[i][j - 1]);
}
for (int i = 0; i <= n; ++i)
f[i] = 1ll * fac[i + m - 1] * invfac[i] % Mod;
Poly::Mul(w, n + 1, f, n + 1, f);
printf("%d", add(calc1(), calc2()));
return 0;
}
正解更加普及的做法是注意到 \(m\) 很小,而算贡献时其余普通石子的相对位置影响不大,考虑 DP,设 \(f_{i, j}\) 表示从左到右填了 \(i\) 个普通石子和 \(j\) 个特殊石子时第一个特殊石子的 \(w\) 之和,最后再将普通石子染色,就可以类似的枚举右端算答案了。
注意此时还需要知道 DP 的最右侧是否是普通石子,因为如果是普通石子我们要保证与序列最右端的石子染上不同色,所以可能要记录一维 \(0/1\) 表示最后填的否是特殊石子。
时间复杂度 \(O(nm)\) 。
#include <bits/stdc++.h>
using namespace std;
const int Mod = 998244353, rt = 3, invrt = (Mod + 1) / 3;
const int N = 2e5 + 7, M = 3e1 + 7;
vector<int> v[N];
int w[N], a[N], fac[N], inv[N], invfac[N], f[N][M][2];
int n, m, c;
inline int add(int x, int y) {
x += y;
if (x >= Mod)
x -= Mod;
return x;
}
inline int dec(int x, int y) {
x -= y;
if (x < 0)
x += Mod;
return x;
}
inline int mi(int a, int b) {
int res = 1;
for (; b; b >>= 1, a = 1ll * a * a % Mod)
if (b & 1)
res = 1ll * res * a % Mod;
return res;
}
inline void prework(int n) {
fac[0] = fac[1] = 1;
inv[0] = inv[1] = 1;
invfac[0] = invfac[1] = 1;
for (int i = 2; i <= n; ++i) {
fac[i] = 1ll * fac[i - 1] * i % Mod;
inv[i] = 1ll * (Mod - Mod / i) * inv[Mod % i] % Mod;
invfac[i] = 1ll * invfac[i - 1] * inv[i] % Mod;
}
}
inline int C(int n, int m) {
return m > n || m < 0 ? 0 : 1ll * fac[n] * invfac[m] % Mod * invfac[n - m] % Mod;
}
signed main() {
freopen("sand.in", "r", stdin);
freopen("sand.out", "w", stdout);
scanf("%d%d%d", &n, &m, &c);
for (int i = 1; i <= n + 1; ++i)
scanf("%d", w + i);
for (int i = 1; i <= c; ++i)
scanf("%d", a + i);
for (int i = 1; i <= c; ++i) {
v[i].resize(a[i] + 1);
for (int j = 1; j <= a[i]; ++j)
scanf("%d", &v[i][j]);
}
prework(n);
for (int i = 0; i <= n; ++i) {
f[i][1][1] = add(f[i][1][1], w[i + 1]);
for (int j = 0; j <= m; ++j)
for (int k = 0; k <= 1; ++k) {
f[i + 1][j][0] = add(f[i + 1][j][0], f[i][j][k]);
f[i][j + 1][1] = add(f[i][j + 1][1], f[i][j][k]);
}
}
int mul = 1;
for (int i = 1; i <= c; ++i)
mul = 1ll * mul * invfac[a[i]] % Mod;
int ans = 1ll * f[n][m][1] * fac[n] % Mod * mul % Mod;
for (int i = 1; i <= c; ++i)
for (int j = 1; j <= a[i]; ++j)
for (int k = 0; k <= 1; ++k)
ans = add(ans, 1ll * v[i][j] * f[n - j][m][k] % Mod *
mul % Mod * fac[a[i]] % Mod * fac[n - a[i]] % Mod * C(n - j - !k, a[i] - j) % Mod);
printf("%d", ans);
return 0;
}
*二叉虚树
这是一道交互题。
交互库有一个 \(n\) 个点的有根二叉树(每个点只有不超过两个儿子)。
给定 \(n\) ,每次询问给出一个点集 \(S\) ,交互库会返回其虚树的大小,即 \(|\{ \mathrm{LCA}(x, y) \mid x, y \in S \}|\) 。
试用 \(m\) 次询问求出树的结构(返回 \(n - 1\) 条边),或报告无法使用有限次操作确定树的结构。
- 子任务一:\(n \leq 500\) ,\(2 \nmid n\) ,\(m = 4500\) 。
- 子任务二:\(500 < n \le 1000\) 或 \(n\) 为偶数,\(m = 10500\) 。
*未来的我
有两个初始为空的序列 \(a, b\) ,\(m\) 次操作,操作有以下类型:
- 给定 \(x, c, p, q, u, v\),分别在数组 \(a,b\) 的第 \(x\) 个数和第 \(x+1\) 个数之间插入 \(c\) 个数,对于 \(1 \le i \le c\),在 \(a\) 中插入的第 \(i\) 个数为 \(p q^{i - 1}\),在 \(b\) 中插入的第 \(i\) 个数为 \(u + v(i - 1)\)。
- 给定 \(l,r\),删除数组 \(a, b\) 的区间 \([l, r]\) 。
- 给定 \(l,r\),翻转数组 \(a, b\) 的区间 \([l, r]\) 。
- 给定 \(l,r\),询问 \(\sum_{i = l}^r a_i b_i\),答案对 \(P=10^9+7\) 取模。
- 给定 \(l,r,u,v\),对数组 \(b\) 的区间 \([l, r]\) 进行修改,对于 \(l \le i \le r\),将 \(b_i\) 修改为 \(b_i + u + v(i - l)\) 。
\(m \leq 3 \times 10^5\)
Day 4 - 陈旭磊
*程序攻击
交互库有 \(u, v, k, r, q\) 五个未知数,给定 \(n, mod\) 。
每次询问可以给出一个 \(n \times n\) 的矩阵 \(A\) ,交互库会执行如下操作:
定义两个数相乘的花费时间为二进制位数的乘积,其中 \(x\) 的二进制位数定义为 \(\mathrm{len}(x) = \begin{cases} 1 & x = 0 \\ \lfloor \log_2(x) \rfloor + 1 & x \ge 1 \end{cases}\) 。
inline int len(int x) { return x ? __lg(x) + 1 : 1; } inline int mul(int x, int y) { return cost += len(x) * len(y), 1ll * x * y % mod; }
交互库先将 \(A_{u, v}\) 赋值为 \(k \times (\sum_{i, j} A_{i, j}) + r\) ,返回矩阵快速幂计算 \(A^q\) 的时间,计算函数如下:
inline Matrix merge(const Matrix &x, const Matrix y) { Matrix z; memset(z.a, 0, sizeof(z.a)); for (int i = 0; i < n; ++i) for (int j = 0; j < n; ++j) for (int k = 0; k < n; ++k) z.a[i][k] = (z.a[i][k] + mul(x.a[i][j], y.a[j][k])) % mod; return z; } inline int get_time(Matrix A) { cost = 0; int s = 0; for (int i = 0; i < n; ++i) for (int j = 0; j < n; ++j) s = (s + A.a[i][j]) % mod; A.a[u][v] = (mul(s, k) + r) % mod; Matrix B; for (int i = 0; i < n; ++i) for (int j = 0; j < n; ++j) B.a[i][j] = (i == j); for (int i = 0; i < 31; ++i) { if (q >> i & 1) B = merge(B, A); A = merge(A, A); } return cost; }
试用 \(\leq 5 \times 10^4\) 次询问给出 \(u, v\) 以及 \(500\) 个可能的 \(q\) ,只要保证有一个 \(q\) 正确即可。
\(n = 2\) ,保证数据随机
先考虑求 \(u, v\) :
- 若 \(u \neq v\) ,以 \(u = 0, v = 1\) 为例,考虑随机生成形如 \(\begin{bmatrix} 0 & x \\ 0 & 0 \end{bmatrix}\) 的矩阵,若 \(u = 0, v = 1\) ,则一次矩阵乘法后就变成全 \(0\) 矩阵,而其他三种情况矩阵内仍有非零值,因此返回结果很小(大概的界是 \(< 1700\))。
- 否则若 \(u = v\) ,考虑随机生成形如 \(\begin{bmatrix} x & 0 \\ 0 & 0 \end{bmatrix}\) 的矩阵,\(u = v = 0\) 时矩阵始终只有一个非零值,而 \(u = v = 1\) 时则有两个,时间差异很大,因此询问 \(\begin{bmatrix} x & 0 \\ 0 & 0 \end{bmatrix}\) 和 \(\begin{bmatrix} 0 & 0 \\ 0 & x \end{bmatrix}\) 比较用时即可。
发现 \(k, r\) 很难求,考虑将赋值操作视为将 \(A_{u, v}\) 赋成随机值,下面考虑求 \(q\) 。
设随机数的期望位数为 \(l\) ,\(s = \mathrm{popcount}(q)\) ,则两个随机矩阵乘法的期望时间为 \(8 l^2\) ,询问一个随机矩阵的期望时间为 \(249 l^2 + (s - 1) \times 8 l^2 + 8l\) 。而 \(l\) 不难算出,因此可以通过若干次询问求出 \(\mathrm{popcount}(q)\) 。
可以发现一次询问的时间由自乘和他乘组成,其中他乘可以反映 \(q\) 的情况。以 \(u = 1, v = 0\) 为例,若输入随机矩阵 \(\begin{bmatrix} x & 0 \\ 0 & 0 \end{bmatrix}\) ,则可以手动得知若干次自乘后左上角的值 \(x'\) 。将另一个有值的位置看做随机数,于是可以轻松算出自乘的期望时间。若此时 \(x'\) 比较小(\(x' < \frac{mod}{16}\)),那么会使得总时间变少。
代入若干随机矩阵,求出总时间减自乘时间期望和的平均数。
对于每个二进制位,定义其权值为其在作为较小的 \(x'\) 的时候时间小于平均值的概率。由于若一次询问存在很多的较小值,我们无法区分是哪一位对时间造成的影响,于是对这里的概率进行加权,认为一次询问的权值是其较小数个数的倒数。
将所有位按权值大小升序排序,显然越靠后的位越有可能是答案,于是考虑将字典序前 \(500\) 大的方案设为可能的 \(q\) 即可。
*灯泡翻转
source:[ARC139F] Many Xor Optimization Problems
给定 \(n, m\) ,对于所有长度为 \(n\) 、元素 \(\in [0, 2^m - 1]\) 的序列,求最大子集异或和的和。
\(n \leq 10^9\) ,\(m \leq 2 \times 10^6\)
星球大战
source:[AGC063E] Child to Parent
给定一棵树,根为 \(1\) ,第 \(i\) 个点的权值为 \(a_i\) 。一次操作可以选择一个 \(u\) 满足 \(u \neq 1\) 且 \(a_u > 0\) ,然后令 \(a_i \to a_i - 1\) ,\(a_{fa_i} \to a_{fa_i} + k\) 。
求若干次操作后不同局面的数量,两个局面不同当且仅当存在一个 \(a_i\) 不同。
\(n \leq 300\)
设 \(c_i\) 表示 \(i\) 位置的操作次数,由于 \(a\) 和 \(c\) 构成双射,因此只需考虑对 \(c\) 计数。对于每个 \(u\) ,限制为 \(0 \le c_i \le a_u + k \times \sum_{v \in son(u)} c_v\) 。
设 \(f_{u, i}\) 表示 \(u\) 子树所有方案是上传权值的 \(i\) 次幂之和。先考虑 \(f_{u, 0}\) 的求法,拆开每个子树的贡献得到:
设 \(c_i\) 表示 \(i\) 位置的操作次数,由于 \(a\) 和 \(c\) 构成双射,因此只需考虑对 \(c\) 计数。对于每个 \(u\) ,限制为 \(0 \le c_i \le a_u + k \times \sum_{v \in son(u)} c_v\) 。
设 \(f_{u, i}\) 表示 \(u\) 子树所有方案是上传权值的 \(i\) 次幂之和。先考虑 \(f_{u, 0}\) 的求法,拆开每个子树的贡献得到:
发现要知道 \(f_{u, 0}\) 就需要知道 \(f_{v, 1}\) ,以此类推。
记 \(F_n(x) = \sum_{i = 0}^x i^n\) ,规定 \(0^0 = 1\) ,则:
由于 \(F_n(x)\) 是一个关于 \(x\) 的 \(n + 1\) 次多项式,因此考虑维护:
即多项式中每一个 \(i\) 次幂项的和,直接拉格朗日插值即可得到 \(f\) 各项系数,那么求出 \(g\) 之后直接通过系数算出 \(f\) 即可。
求 \(g\) 的系数需要合并子树信息,直接用二项式定理即可。
时间复杂度 \(O(n^3)\) 。
#include <bits/stdc++.h>
using namespace std;
const int Mod = 998244353;
const int N = 3e2 + 7;
struct Graph {
vector<int> e[N];
inline void insert(int u, int v) {
e[u].emplace_back(v);
}
} G;
int fa[N], a[N], C[N][N], pw[N], f[N][N], g[N][N], h[N];
int n, m;
inline int add(int x, int y) {
x += y;
if (x >= Mod)
x -= Mod;
return x;
}
inline int dec(int x, int y) {
x -= y;
if (x < 0)
x += Mod;
return x;
}
inline int mi(int a, int b) {
int res = 1;
for (; b; b >>= 1, a = 1ll * a * a % Mod)
if (b & 1)
res = 1ll * res * a % Mod;
return res;
}
inline void prework() {
for (int i = C[0][0] = 1; i <= n; ++i)
for (int j = C[i][0] = 1; j <= i; ++j)
C[i][j] = add(C[i - 1][j], C[i - 1][j - 1]);
pw[0] = 1;
for (int i = 1; i <= n; ++i)
pw[i] = 1ll * pw[i - 1] * m % Mod;
}
namespace Lagrange {
int h[N][N], x[N], y[N], g[N];
inline void solve(int *x, int *y, int n, int *f) {
memset(g, 0, sizeof(int) * (n + 1)), g[0] = 1;
for (int i = 1; i <= n; ++i)
for (int j = i; ~j; --j)
g[j] = add(1ll * dec(0, x[i]) * g[j] % Mod, j ? g[j - 1] : 0);
memset(f, 0, sizeof(int) * n);
for (int i = 1; i <= n; ++i) {
int mul = 1;
for (int j = 1; j <= n; ++j)
if (j != i)
mul = 1ll * mul * dec(x[i], x[j]) % Mod;
mul = 1ll * y[i] * mi(mul, Mod - 2) % Mod;
for (int j = n - 1, res = g[n]; ~j; --j)
f[j] = add(f[j], 1ll * mul * res % Mod), res = add(g[j], 1ll * x[i] * res % Mod);
}
}
inline void prework() {
for (int i = 0; i <= n; ++i) {
y[0] = !i;
for (int j = 1; j <= i + 2; ++j)
x[j] = j, y[j] = add(y[j - 1], mi(j, i));
solve(x, y, i + 2, h[i]);
}
}
inline int query(int x, int n) {
int ans = 0;
for (int i = 0, pw = 1; i <= n + 1; ++i, pw = 1ll * pw * x % Mod)
ans = add(ans, 1ll * h[n][i] * pw % Mod);
return ans;
}
} // namespace Lagrange
void dfs(int u) {
if (G.e[u].empty()) {
for (int i = 0; i <= n; ++i)
f[u][i] = Lagrange::query(a[u], i);
return;
}
for (int v : G.e[u])
dfs(v);
if (u == 1)
return;
for (int i = 0; i <= n; ++i)
g[u][i] = mi(a[u], i);
for (int v : G.e[u]) {
memset(h, 0, sizeof(int) * (n + 1));
for (int i = 0; i <= n; ++i)
for (int j = 0; i + j <= n; ++j)
h[i + j] = add(h[i + j], 1ll * C[i + j][i] * pw[i] % Mod * f[v][i] % Mod * g[u][j] % Mod);
memcpy(g[u], h, sizeof(int) * (n + 1));
}
for (int i = 0; i <= n; ++i)
for (int j = 0; j <= i + 1; ++j)
f[u][i] = add(f[u][i], 1ll * Lagrange::h[i][j] * g[u][j] % Mod);
}
signed main() {
scanf("%d", &n);
for (int i = 2; i <= n; ++i)
scanf("%d", fa + i), G.insert(fa[i], i);
scanf("%d", &m);
for (int i = 1; i <= n; ++i)
scanf("%d", a + i);
prework(), Lagrange::prework(), dfs(1);
int ans = 1;
for (int x : G.e[1])
ans = 1ll * ans * f[x][0] % Mod;
printf("%d", ans);
return 0;
}
Day 5 - 陈诺
Eileen 的游戏
source:P12558 [UOI 2024] Heroes and Monsters
给定 \(a_{1 \sim n}, b_{1 \sim n}\) ,定义 \(f_k\) 表示满足如下条件的集合 \(S\) 的数量:
- \(S \subseteq \{ 1, 2, \cdots, k \}\) ,\(|S| = k\) 。
- 存在 \(1 \sim n\) 的排列 \(p\) ,满足 \(\forall i \in S, a_i > b_{p_i}\) ,\(\forall i \notin S, a_i < b_{p_i}\) 。
\(q\) 次询问 \(\sum_{i = l}^r f_i\) 。
\(n \leq 5000\) ,\(a_{1 \sim n}, b_{1 \sim n}\) 两两不同
先考虑如何判断 \(S\) 的可行性,考虑贪心,记 \(T = \{ 1, 2, \cdots, k \} \setminus S\) ,将 \(a, b\) 升序排序后需要满足 \(a_{S_i} > b_i\) 且 \(a_{T_i} < b_{|S| + i}\) 。
枚举 \(|S|\) ,设 \(f_{i, j}\) 表示考虑前 \(i\) 个 \(a\) 、选了 \(j\) 个的方案数,时间复杂度 \(O(n^3)\) 。
考虑优化,按 \(b_{|S|}\) 将 \(a\) 分成两部分,则前一部分显然可以放入 \(T\) ,后一部分显然可以放入 \(S\) 。因此只需考虑前一部分是否放入 \(S\) ,后一部分是否放入 \(T\) 即可。
这样限制就拆分开来了,只要对前后缀分别 DP 一次即可,每次统计答案就做一次卷积,时间复杂度 \(O(n^2)\) 。
#include <bits/stdc++.h>
using namespace std;
const int Mod = 998244353;
const int N = 5e3 + 7;
int a[N], b[N], f[N][N], g[N][N], ans[N];
int n, q;
inline int add(int x, int y) {
x += y;
if (x >= Mod)
x -= Mod;
return x;
}
inline int dec(int x, int y) {
x -= y;
if (x < 0)
x += Mod;
return x;
}
signed main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%d", a + i);
for (int i = 1; i <= n; ++i)
scanf("%d", b + i);
sort(a + 1, a + n + 1), sort(b + 1, b + n + 1);
f[0][0] = 1;
for (int i = 1; i <= n; ++i)
for (int j = 0; j <= i; ++j)
f[i][j] = add(f[i - 1][j], j && a[i] > b[j] ? f[i - 1][j - 1] : 0);
g[n + 1][0] = 1;
for (int i = n; i; --i)
for (int j = 0; j <= n - i + 1; ++j)
g[i][j] = add(j ? g[i + 1][j - 1] : 0, a[i] < b[i + j] ? g[i + 1][j] : 0);
for (int i = 0, j = 0; i <= n; ++i) {
while (j < n && a[j + 1] < b[i])
++j;
for (int k = 0; k <= i; ++k)
ans[i] = add(ans[i], 1ll * f[j][k] * g[j + 1][i - k] % Mod);
}
for (int i = 1; i <= n; ++i)
ans[i] = add(ans[i], ans[i - 1]);
scanf("%d", &q);
while (q--) {
int l, r;
scanf("%d%d", &l, &r);
printf("%d\n", dec(ans[r], l ? ans[l - 1] : 0));
}
return 0;
}
*Diana 的梦境
Soyo 的秘密
source:QOJ8416. Dzielniki [B]
交互库有一个正整数 \(x \in [1, n]\) ,每次询问可以给出一个 \(y \in [0, C]\) ,交互库会返回 \(x + y\) 的因数个数。
共 \(10\) 组数据,试用总共 \(\leq 720\) 次询问求出 \(x\) ,交互库不自适应。
\(n = 10^{14}\) ,\(C = 10^{17}\)
首先,若 \(2^k \mid n\) 且 \(2^{k + 1} \nmid n\) ,则 \((k + 1) \mid d(n)\) 。
考虑求出 \(x\) 在模 \(2^{47}\) 意义下的值,每次尝试在得知 \(x \bmod 2^k\) 的情况下推出 \(x \bmod 2^{k + 1}\) 的值,初始时 \(x \bmod 2^0 = 0\) ,每次:
- 若询问 \(y = 2^k - (x \bmod 2^k)\) 返回值为 \((k + 1)\) 的倍数,则认为 \(x \bmod 2^{k + 1} = x \bmod 2^k\) 。
- 若询问 \(y = 2^{k + 1} - (x \bmod 2^k)\) 返回值为 \((k + 1)\) 的倍数,则认为 \(x \bmod 2^{k + 1} = (x \bmod 2^k) + 2^k\) 。
- 否则说明当前的值不为 \(x \bmod 2^k\) ,这是因为前面递归时有概率是假的。
对询问记忆化即可通过。
#include <bits/stdc++.h>
#include "divisors.h"
typedef long long ll;
using namespace std;
map<ll, ll> mp;
ll n;
inline ll query(ll x) {
return mp.find(x) == mp.end() ? mp[x] = Ask(x) : mp[x];
}
bool dfs(ll x, int k) {
if ((1ll << k) > n) {
Answer(x);
return true;
}
if (!(query((1ll << k) - x) % (k + 1)) && dfs(x, k + 1))
return true;
else if (!(query((1ll << (k + 1)) - x) % (k + 1)) && dfs(x + (1ll << k), k + 1))
return true;
else
return false;
}
signed main() {
n = GetN();
int T = GetT();
while (T--)
mp.clear(), dfs(0, 0);
return 0;
}
Day 6 - 李静榕
数组排序
随机生成一个长度为 \(n\) 、值域为 \([1, m]\) 的序列 \(a\) ,将其排序后记差分数组为 \(b_i = a'_i - a'_{i -1}\) ,将 \(b\) 排序后记前缀和数组为 \(c_i = \sum_{j = 1}^i b'_j\) 。对于 \(i = 1, 2, \cdots, n\) ,求 \(c_i\) 的期望。
\(n \leq 5000\) ,\(m \leq 10^6\)
首先拆期望:\(E(c_i) = E(b'_i) - E(b'_{i - 1})\) 。考虑求 \(b'_i \geq k\) 的方案数,即至少 \(i\) 个 \(b \geq k\) 的方案数,而这可以通过求恰好 \(i\) 个 \(b \geq k\) 的方案数后做前缀和得到。
考虑二项式反演,钦定 \(i\) 个 \(b \geq k\) 的方案数是好求的,直接做可以做到 \(O(m n^2)\) 。
考虑把所有钦定值的二项式反演拿到外面一起做,时间复杂度 \(O(m \ln m + n^2)\) 。
另一种方法是由于 \(b'_i\) 即为 \(b\) 的第 \(i\) 小值,然后上 kth-Min-Max 容斥即可。
#include <bits/stdc++.h>
using namespace std;
const int Mod = 998244353;
const int N = 5e3 + 7, M = 1e6 + 7;
int fac[M], inv[M], invfac[M], ans[N], f[N], g[N];
int n, m;
inline int add(int x, int y) {
x += y;
if (x >= Mod)
x -= Mod;
return x;
}
inline int dec(int x, int y) {
x -= y;
if (x < 0)
x += Mod;
return x;
}
inline int sgn(int n) {
return n & 1 ? Mod - 1 : 1;
}
inline void prework(int n) {
fac[0] = fac[1] = 1;
inv[0] = inv[1] = 1;
invfac[0] = invfac[1] = 1;
for (int i = 2; i <= n; ++i) {
fac[i] = 1ll * fac[i - 1] * i % Mod;
inv[i] = 1ll * (Mod - Mod / i) * inv[Mod % i] % Mod;
invfac[i] = 1ll * invfac[i - 1] * inv[i] % Mod;
}
}
inline int C(int n, int m) {
return m > n || m < 0 ? 0 : 1ll * fac[n] * invfac[m] % Mod * invfac[n - m] % Mod;
}
inline int invC(int n, int m) {
return m > n || m < 0 ? 0 : 1ll * invfac[n] * fac[m] % Mod * fac[n - m] % Mod;
}
signed main() {
freopen("sort.in", "r", stdin);
freopen("sort.out", "w", stdout);
scanf("%d%d", &n, &m), prework(m);
for (int num = 1; num <= m; ++num)
for (int i = 1; i <= min(n, m / num); ++i)
g[i] = add(g[i], 1ll * C(n, i) * C(m - i * (num - 1), n) % Mod);
for (int i = 1; i <= n; ++i) {
f[i] = 0;
for (int j = i; j <= n; ++j)
f[i] = add(f[i], 1ll * sgn(j - i) * C(j, i) % Mod * g[j] % Mod);
}
for (int i = n, s = 0; i; --i)
ans[n - i + 1] = add(ans[n - i + 1], s = add(s, f[i]));
for (int i = 1; i <= n; ++i)
printf("%d\n", 1ll * (ans[i] = add(ans[i], ans[i - 1])) * invC(m, n) % Mod);
return 0;
}
最短路径
给定一张 \(n \times m\) 的网格图,每个点 \((i, j)\) 可以移动到 \((i - 1, j)\) 、\((i + 1, j)\) 、\((i, j + 1)\) 中的一个位置(需满足在网格图内),每条横纵边带边权。
对于 \(i = 1, 2, \cdots, n\) ,求 \(\sum_{j = 1}^n \mathrm{dist}((i, 1), (j, m))\) ,其中 \(\mathrm{dist}\) 表示最短路。
\(n \times m \le 2 \times 10^5\)
考虑起点向下移动时 SPT 的变化,显然每个点的父亲只会不断向下移动。
考虑分治,假设已经求出 \(i = l\) 和 \(i = r\) 的 SPT,若某个点的父亲在这两棵树上相同,则 \(i = l, l + 1, \cdots, r\) 时该点的父亲都相同,可以将其与父亲缩起来。
由于每条边只会在 \(O(\log n)\) 个分治区间出现,使用线性求 SPT 的做法(从后往前做类似前缀和优化的操作),时间复杂度 \(O(nm \log(nm))\) 。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const int N = 2e5 + 7;
struct DSU {
int fa[N];
inline void prework(int n) {
iota(fa + 1, fa + n + 1, 1);
}
inline int find(int x) {
while (x != fa[x])
fa[x] = fa[fa[x]], x = fa[x];
return x;
}
inline void merge(int x, int y) {
fa[find(y)] = find(x);
}
} dsu;
vector<int> a[N], b[N];
ll ans[N];
int n, m;
struct SPT {
vector<tuple<int, int, ll> > edg;
vector<pair<int, ll> > ans;
vector<ll> dis;
int siz;
inline SPT() : siz(0) {}
inline void build(int s) {
dis.assign(siz + 1, inf), dis[s] = 0;
for (auto it : edg)
dis[get<1>(it)] = min(dis[get<1>(it)], dis[get<0>(it)] + get<2>(it));
}
inline SPT(int s) {
siz = n * m, ans.assign(siz + 1, make_pair(0, 0ll));
auto getid = [&](int x, int y) {
return (x - 1) * n + y;
};
for (int i = 1; i <= n; ++i)
ans[getid(m, i)].first = 1;
for (int i = 1; i <= m; ++i) {
for (int j = 1; j < n; ++j)
edg.emplace_back(getid(i, j), getid(i, j + 1), b[j][i]);
for (int j = n - 1; j; --j)
edg.emplace_back(getid(i, j + 1), getid(i, j), b[j][i]);
if (i < m) {
for (int j = 1; j <= n; ++j)
edg.emplace_back(getid(i, j), getid(i + 1, j), a[j][i]);
}
}
build(s);
}
inline ll calc() {
ll res = 0;
for (int i = 1; i <= siz; ++i)
res += dis[i] * ans[i].first + ans[i].second;
return res;
}
};
inline SPT maintain(SPT A, SPT B) {
SPT C;
dsu.prework(A.siz);
auto ans = A.ans;
for (auto it : A.edg) {
int u = get<0>(it), v = get<1>(it);
ll w = get<2>(it);
if (dsu.find(v) == v && A.dis[u] + w == A.dis[v] && B.dis[u] + w == B.dis[v]) {
dsu.merge(u = dsu.find(u), v);
ans[u].first += ans[v].first, ans[u].second += ans[v].second + w * ans[v].first;
}
}
vector<int> id(A.siz + 1, -1);
C.ans = {make_pair(0, 0)}, C.dis = {0};
for (int i = 1; i <= A.siz; ++i)
if (dsu.find(i) == i)
id[i] = ++C.siz, C.ans.emplace_back(ans[i]), C.dis.emplace_back(A.dis[i]);
for (auto it : A.edg) {
int u = get<0>(it), v = get<1>(it);
ll w = get<2>(it);
if (dsu.find(v) == v)
C.edg.emplace_back(id[dsu.find(u)], id[v], w + A.dis[u] - A.dis[dsu.find(u)]);
}
return C;
}
void solve(int l, int r, SPT Gl, SPT Gr) {
if (l + 1 == r) {
ans[l] = Gl.calc();
return;
}
int mid = (l + r) >> 1;
SPT Gm = Gl;
Gm.build(mid - l + 1);
solve(l, mid, maintain(Gl, Gm), maintain(Gm, Gl));
solve(mid, r, maintain(Gm, Gr), maintain(Gr, Gm));
}
signed main() {
freopen("path.in", "r", stdin);
freopen("path.out", "w", stdout);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
a[i].resize(m), b[i].resize(m + 1);
for (int i = 1; i <= n; ++i)
for (int j = 1; j < m; ++j)
scanf("%d", &a[i][j]);
for (int i = 1; i < n; ++i)
for (int j = 1; j <= m; ++j)
scanf("%d", &b[i][j]);
if (n == 1)
return printf("%lld", SPT(1).calc()), 0;
SPT Gl(1), Gr(n);
ans[n] = Gr.calc(), solve(1, n, Gl, Gr);
for (int i = 1; i <= n; ++i)
printf("%lld\n", ans[i]);
return 0;
}
*因式计数
给定一个模 \(2\) 意义下的 \(n\) 次多项式,求其因式个数,其中多项式 \(G\) 是多项式 \(F\) 的因式当且仅当存在多项式 \(H\) 满足 \(G \times H \equiv F \pmod{2}\) 。
\(n \leq 2000\)
Day 7 - 李静榕
排列
给定一个 \(1 \sim n\) 的排列,一次操作可以交换两个数的位置,需要满足交换完后逆序对数减少。求若干次操作后可以得到的排列数量。
\(n \le 20\)
首先可以发现,合法的交换等价于交换一对逆序对。
考虑直接找合法排列的充要条件:对于每个 \(k\) ,每个 \(b\) 的前缀中 \(\leq k\) 的数的数量必须不小于 \(a\) 中相应前缀 \(\leq k\) 的数的数量。
设 \(f_S\) 表示 \(1 \sim |S|\) 放的位置集合为 \(S\) 的方案数,判定 \(S\) 合法性后枚举上一步填的数转移而来即可,时间复杂度 \(O(2^n n)\) 。
#include <bits/stdc++.h>
using namespace std;
const int Mod = 1e9 + 7;
const int N = 21;
int a[N], p[N], f[1 << N];
int n;
inline int add(int x, int y) {
x += y;
if (x >= Mod)
x -= Mod;
return x;
}
inline int dec(int x, int y) {
x -= y;
if (x < 0)
x += Mod;
return x;
}
signed main() {
freopen("permutation.in", "r", stdin);
freopen("permutation.out", "w", stdout);
scanf("%d", &n);
for (int i = 0; i < n; ++i) {
int x;
scanf("%d", &x);
p[--x] = i;
}
f[0] = 1;
for (int s = 1; s < (1 << n); ++s) {
vector<int> a(n), b(n);
for (int i = 0; i < __builtin_popcount(s); ++i)
++a[p[i]];
for (int i = 0; i < n; ++i)
if (s >> i & 1)
++b[i];
bool flag = (b[0] >= a[0]);
for (int i = 1; i < n && flag; ++i)
a[i] += a[i - 1], b[i] += b[i - 1], flag &= (b[i] >= a[i]);
if (!flag)
continue;
for (int i = 0; i < n; ++i)
if (s >> i & 1)
f[s] = add(f[s], f[s ^ (1 << i)]);
}
printf("%d", f[(1 << n) - 1]);
return 0;
}
*字符串
source:LOJ3396. 「2020-2021 集训队作业」GDSOI2019 novel 加强版
给定 \(n\) 个字符串 \(S_{1 \sim n}\) ,第 \(i\) 个字符串有权值 \(w_i\) 。设:
- \(f(S, T)\) 表示 \(T\) 在 \(S\) 中的出现次数。
- \(g(S) = \sum_{i = 1}^n w_i \times f(S, S_i)\) 。
- \(h(S) = \max_{1 \le l \le r \le |S|} \frac{g(S[l, r])}{r - l + 1}\) 。
对于所有 \(1 \le i \le n, 1 \le j \le |S_i|\) ,求 \(h(S_i[1, j])\) 。
\(\sum_{i = 1}^n |S_i| \leq 5 \times 10^6\) ,\(n \leq 2 \times 10^5\) ,字符集为 \(\text{a, b, c, d}\)
*回文
给定 \(n\) 个字符串 \(S_{1 \sim n}\) 和 \(m\) 个字符串 \(T_{1 \sim m}\) ,记 \(f(S)\) 表示 \(S\) 最长奇回文子串的半径长度,求:
\[\sum_{i = 1}^n \sum_{j = 1}^m f(S_i + T_i) \]\(n, m, \max(\sum |S_i|, \sum |T_i|) \leq 4 \times 10^5\)