闲话 23.3.17
闲话
今日闲话【碎片】
打每日刷新的副本时可能随机掉落,当且仅当今日有模拟赛时掉落概率不为 0。
怎么这几天的闲话阅读量单调递减呢?
没人想看我的垃圾博客;;
模拟赛
口胡万岁!
T1 \(%计数最大向量不超过 n 的异或空间。\)
考虑把空间和基对应,为方便,我们只计算最简阶梯矩阵。同样的,一组基的张成中最大向量就是这组基的加和,这启发我们作数位 dp。
设 \(f(i, j, b)\) 表示填到第 \(i\) 位,基大小为 \(j\),卡上界当 \([b]\) 为真。设当前位的上界是 \(\text{bnd}\),考虑是否卡界/添加主元可以得到
总复杂度 \(O(\log^2 n)\)。
code
#include <bits/stdc++.h>
using namespace std;
using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long;
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
template<typename T1, typename T2> T1 max(T1 a, T2 b) { return a > b ? a : b; }
template<typename T1, typename T2> T1 min(T1 a, T2 b) { return a < b ? a : b; }
#define multi int T; cin >> T; while ( T -- )
#define timer cerr << 1. * clock() / CLOCKS_PER_SEC << '\n';
#define iot ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
#define eb emplace_back
#define pb pop_back
const int N = 45;
int n, f[N][N][2];
int main() {
iot; cin >> n;
f[30][0][1] = 1;
pre(i,30,1) rep(j,0,30) rep(k,0,1) if (f[i][j][k]) {
int top = k ? (n >> (i - 1) & 1) : 1;
addi(f[i - 1][j][k && 0 == top], (1ll << max(j - 1, 0)) * f[i][j][k] % mod);
if (top == 1) {
addi(f[i - 1][j + 1][k], f[i][j][k]);
if (j > 0) addi(f[i - 1][j][k], (1ll << j - 1) * f[i][j][k] % mod);
}
}
int ans = 0;
rep(i,0,30) addi(ans, f[0][i][0], f[0][i][1]);
cout << ans << '\n';
}
T2 \(%计数第一棵树上是联通块、第二棵树上是链的点标号集合。\)
首先有个假做法是点分治并用可撤销并查集拼起来,因为两条链可以拼合。
联通块的充要条件是点集 \(-\) 生成边集 \(=1\),因为是树。
考虑把一条 \(T_2\) 上路径 \(u\to v\) 映射到二维平面上的一个点 \((\text{dfn}[u],\text{dfn}[v])\)。
考虑 \(T_1\) 的每个元素对这个平面的贡献。对一条边 \((u, v)\),包含它的所有路径组成了 \(O(1)\) 个矩形,因为两个端点的可行区间在 dfn 序上是连续(分段连续)的。对一个点 \(u\) 枚举一下出边,能发现有 \(O(出边个数)\) 个矩形。
因此我们就把这 \(T_1\) 上每个联通块对链的贡献转化成了矩形上的 \(O(n)\) 次加减,点加边减。统计平面上值为 \(1\) 的点数即可,这可以通过线段树维护最小值和数量,作扫描线得到。
总时间复杂度 \(O(n\log n)\)。
code
#include <bits/stdc++.h>
using namespace std;
using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long;
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
template<typename T1, typename T2> T1 max(T1 a, T2 b) { return a > b ? a : b; }
template<typename T1, typename T2> T1 min(T1 a, T2 b) { return a < b ? a : b; }
#define multi int T; cin >> T; while ( T -- )
#define timer cerr << 1. * clock() / CLOCKS_PER_SEC << '\n';
#define iot ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
#define eb emplace_back
#define pb pop_back
const int N = 1e5 + 10;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int n, t1, t2, u[N], v[N];
vector<tuple<int, int, int>> vc[N];
ll ans;
vi g[N];
int siz[N], dep[N], dfn[N], stp, st[N][21];
void dfs(int u, int ft) {
siz[u] = 1, dep[u] = dep[ft] + 1, dfn[u] = ++ stp, st[u][0] = ft;
rep(j,1,20) st[u][j] = st[st[u][j - 1]][j - 1];
for (auto v : g[u]) if (v != ft) dfs(v, u), siz[u] += siz[v];
}
inline int getson(int u, int v) {
pre(j,20,0) if (dfn[st[u][j]] > dfn[v]) u = st[u][j];
return u;
}
struct sgt {
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mnv(p) seg[p].mnv
#define cnt(p) seg[p].cnt
#define lzy(p) seg[p].lzy
struct node {
int mnv, cnt, lzy;
} seg[N << 2];
inline void ps_p(int p) {
mnv(p) = min(mnv(ls), mnv(rs));
cnt(p) = cnt(ls) * (mnv(ls) == mnv(p)) + cnt(rs) * (mnv(rs) == mnv(p));
}
inline void ps_d(int p) {
if (!lzy(p)) return;
mnv(ls) += lzy(p), lzy(ls) += lzy(p);
mnv(rs) += lzy(p), lzy(rs) += lzy(p);
lzy(p) = 0;
}
void build(int p, int l, int r) {
mnv(p) = lzy(p) = 0, cnt(p) = 1;
if (l == r) return;
int mid = l + r >> 1;
build(ls, l, mid), build(rs, mid + 1, r);
ps_p(p);
}
void update(int p, int l, int r, int L, int R, int va) {
if (L <= l and r <= R) return static_cast<void>(mnv(p) += va, lzy(p) += va);
ps_d(p); int mid = l + r >> 1;
if (L <= mid) update(ls, l, mid, L, R, va);
if (mid < R) update(rs, mid + 1, r, L, R, va);
ps_p(p);
}
} Tr;
inline void insert(int l1, int r1, int l2, int r2, int va) {
if (l1 <= r1 and l2 <= r2)
vc[l1].eb(l2, r2, va), vc[r1 + 1].eb(l2, r2, -va);
}
signed main() {
iot;
multi {
cin >> n;
rep(i,2,n) cin >> u[i] >> v[i];
rep(i,2,n) cin >> t1 >> t2, g[t1].eb(t2), g[t2].eb(t1);
dfs(1, 0);
rep(u,1,n) {
for (auto v : g[u]) {
if (v == st[u][0]) insert(1, dfn[u] - 1, dfn[u], dfn[u] + siz[u] - 1, 1), insert(dfn[u] + siz[u], n, dfn[u], dfn[u] + siz[u] - 1, 1);
else insert(dfn[v], dfn[v] + siz[v] - 1, 1, dfn[v] - 1, 1), insert(dfn[v], dfn[v] + siz[v] - 1, dfn[v] + siz[v], n, 1);
} insert(dfn[u], dfn[u], 1, n, 1);
}
rep(i,2,n) {
if (dfn[u[i]] < dfn[v[i]]) swap(u[i], v[i]);
if (dfn[u[i]] < dfn[v[i]] + siz[v[i]]) {
int t = getson(u[i], v[i]);
insert(dfn[u[i]], dfn[u[i]] + siz[u[i]] - 1, 1, dfn[t] - 1, -1), insert(dfn[u[i]], dfn[u[i]] + siz[u[i]] - 1, dfn[t] + siz[t], n, -1);
insert(1, dfn[t] - 1, dfn[u[i]], dfn[u[i]] + siz[u[i]] - 1, -1), insert(dfn[t] + siz[t], n, dfn[u[i]], dfn[u[i]] + siz[u[i]] - 1, -1);
} else {
insert(dfn[u[i]], dfn[u[i]] + siz[u[i]] - 1, dfn[v[i]], dfn[v[i]] + siz[v[i]] - 1, -1);
insert(dfn[v[i]], dfn[v[i]] + siz[v[i]] - 1, dfn[u[i]], dfn[u[i]] + siz[u[i]] - 1, -1);
}
}
Tr.build(1, 1, n);
rep(i,1,n) {
for (auto [l, r, va] : vc[i]) Tr.update(1, 1, n, l, r, va);
// cout << Tr.seg[1].mnv << ' ' << Tr.seg[1].cnt << '\n';
if (Tr.seg[1].mnv == 1) ans += Tr.seg[1].cnt;
}
cout << (ans + n) / 2 << '\n';
rep(i,1,n) g[i].clear(), vc[i].clear(); ans = stp = 0;
}
}
T3 \(%给字符串 S。计数最少的间隔符加入数,使得:给 q 次一个字符串 S 的子串,加入间隔符后不存在这个子串。\)
考虑确定一个询问子串的情况。我们可以首先找到所有出现位置 \(S = \{ [l_i, r_i]\}\),随后可以贪心地算出最少加入数。具体地,我们每次找最左边的区间,在他右端点前放一个间隔符,并删除所有包含这间隔符的区间即可。这个上线段树是 \(O(|S| \log n)\) 的,用 radix sort + umap 是 \(O(|S|)\) 的。
第二种做法具体如下:
设当前拿到的区间集合是 \(S = \{ [l_i, r_i]\}\),这是左值+右值各 \(|S|\) 个。首先把 \(2|S|\) 个值 radix sort,从小到大扫值域。如果当前值是左值,把对应区间加入一个 umap。otherwise 给 umap 里所有值打标记并清空 umap,若 umap 清空前不空就答案加 \(1\);如果这个右值对应的左值被打了标记就不清空,略过即可。容易发现这做法是 \(O(|S|)\) 的。
我们自然想到了根号分治。
对于 \(\le B\) 的询问,我们发现可能的总串数是 \(O(nB)\) 的,可以 \(O(nB)\) 地预处理。具体地,我们对每个 \(\le B\) 的长度 \(k\),找到 \(n - k\) 个可能的串,每次拿出所有相等的串作如上的贪心。这样总复杂度是 \(O(nB) - O(1)\) 的。
对于 \(\ge B\) 的询问,如果暴力按上面做法做很可能劣化到单次 \(O(n)\)。但是从上面的想法我们能看出一个性质:若长度 \(\ge B\),清空 umap 的次数不超过 \(n / B\)。这告诉我们,上线段树的总时间复杂度是 \(O(n\log n /B)\) 的。这样我们就只需要快速找到所有右端点。
不难发现这右端点本质上是询问串的 endpos,因此得到原串的后缀链接树后即可在 \(O(n\log n)\) 的复杂度内通过 SAM 上线段树合并的方式得到每个询问串的 endpos 集合。随后在 SAM 上查出串,用线段树做贪心即可。
视 \(q = O(n)\),总时间复杂度为 \(O(nB + n\log n / B)\),取 \(O(\sqrt{n\log n})\) 得到时间复杂度 \(O(n\sqrt{n\log n})\)。
code
#include <bits/stdc++.h>
using namespace std;
using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long;
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
template<typename T1, typename T2> T1 max(T1 a, T2 b) { return a > b ? a : b; }
template<typename T1, typename T2> T1 min(T1 a, T2 b) { return a < b ? a : b; }
#define multi int T; cin >> T; while ( T -- )
#define timer cerr << 1. * clock() / CLOCKS_PER_SEC << '\n';
#define iot ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
#define eb emplace_back
#define pb pop_back
const int N = 2e5 + 5, B = 450;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int n, m, sqr, ret[N][B];
char S[N];
vi vec[N], g[N];
struct sgt {
#define ls(p) seg[p].l
#define rs(p) seg[p].r
int mlc, rt[N];
struct node {
int l, r;
} seg[N << 6];
void update(int& p, int l, int r, int x) {
if (!p) p = ++ mlc;
if (l == r) return;
int mid = l + r >> 1;
if (x <= mid) update(ls(p), l, mid, x);
else update(rs(p), mid + 1, r, x);
}
int merge(int x, int y) {
if (!x or !y) return x | y;
int ret = ++mlc;
ls(ret) = merge(ls(x), ls(y));
rs(ret) = merge(rs(x), rs(y));
return ret;
}
int query(int p, int l, int r, int x) {
if (!p) return 0;
if (l == r) return l;
int mid = l + r >> 1;
if (x > mid) return query(rs(p), mid + 1, r, x);
int ret = query(ls(p), l, mid, x);
if (ret) return ret;
return query(rs(p), mid + 1, r, x);
}
} Tr;
int son[N][26], link[N], len[N], pos[N], mlc = 1, lst = 1;
void extend(int c, int id) {
int now = ++ mlc, p = lst;
len[now] = len[lst] + 1;
while (p and !son[p][c])
son[p][c] = now, p = link[p];
if (p == 0) link[now] = 1;
else {
int q = son[p][c];
if (len[p] + 1 == len[q]) link[now] = q;
else {
int kage = ++ mlc;
len[kage] = len[p] + 1, link[kage] = link[q];
memcpy(son[kage], son[q], sizeof(son[q]));
while (p and son[p][c] == q)
son[p][c] = kage, p = link[p];
link[q] = link[now] = kage;
}
}
pos[id] = lst = now;
}
int fa[N][21];
void dfs(int u) {
rep(i,1,20) fa[u][i] = fa[fa[u][i - 1]][i - 1];
for(int v : g[u]) {
dfs(v);
if (len[u] > sqr) Tr.rt[u] = Tr.merge(Tr.rt[u], Tr.rt[v]);
}
}
int find(int u, int d) {
pre(i,20,0) if (len[ fa[u][i] ] >= d) u = fa[u][i]; return u;
}
signed main () {
iot;
cin >> n >> m >> S + 1;
sqr = sqrt(n * log(n));
rep(i,1,n) extend(S[i] - 'a', i);
rep(i,1,mlc) fa[i][0] = link[i], g[link[i]].eb(i);
rep(i,1,n) Tr.update(Tr.rt[pos[i]], 1, n, i);
rep(i,1,n) for(int u = find(pos[i], sqr); u; u = link[u]) vec[u].eb(i);
dfs(1);
rep(i,1,mlc) rep(d,len[link[i]] + 1,min(len[i],sqr)) {
int p = 1;
for(auto it : vec[i]) if (it - d + 1 >= p) ++ret[i][d], p = it;
}
rep(i,1,m) {
int l, r;
cin >> l >> r;
if (l == r) { cout << -1 << "\n"; continue; }
int d = r - l + 1, u = find(pos[r], d);
if (d <= sqr) cout << ret[u][d] << "\n";
else {
int p = 1, ret = 0;
while(p + d - 1 <= n and (p = Tr.query(Tr.rt[u], 1, n, p + d - 1))) ++ ret;
cout << ret << "\n";
}
}
}
杂题
有 \(m\) 张牌,其中一张是王牌。现在你执行 \(n\) 次如下操作:洗牌后查看第一张牌是什么。
令 \(x\) 为洗牌后第一张牌为王牌的次数,现在假设洗牌时 \(m!\) 种牌的排列出现的概率均相等,求 \(x^k\) 的期望值。
\(1\le n, m \le 998244532, 1\le k\le 10^5\)。
考虑出现王牌的概率是 \(m^{-1}\),我们可以简单地枚举出现王牌的次数以及对答案的贡献。答案就是
可以 \(O(k\log k)\) 地求得。
有两个棋子初始点都在坐标 \(0\),两个棋子之间没有区别,总共要进行 \(n\) 次操作,每次操作是如下操作其中之一:
选择一个棋子向前移动一步。
选择位置较后的那个棋子,直接移动到位置较前的那个棋子的位置。
计数 \(n\) 次操作后两个棋子分别在位置 \(a,b\) 的方案,对 \(998244353\) 取模。两种方案是相同的,当且仅当两个棋子在每一步的坐标都是相同的(注意不是每一步的操作都相同)。
\(1\le a\le b\le n \le 10^7\)。
又到了今天的生成函数时间!
什么?你看这题面不像?我也看着不像。
但是确实可以。
约定一个高维生成函数 \(F(x_1, \dots, x_k)\) 的第 \(x_1 \dots x_k\) 项记为 \(F[x_1, \dots, x_k]\)。
欸,你别害怕啊!\(%完全疯狂!\)
你看 2. 操作不是很好刻画。那就不刻画了,先找性质。
可以发现虽然 2. 操作执行次数不定,但是其对两个棋子的影响是一样的,这也就让我们可以先只考虑 1. 操作(可以为空),再考虑加一个 \(2\) 操作的影响,用 \(\text{SEQ}\) 构造拼起来。
现在只讨论 1. 操作。首先扔到二维平面上,然后就是只能向右向上走,问有多少种走法从 \((0, 0)\) 到 \((x, y) \text{ s.t. } x < y\) 且不经过 \(y = x\);这是由于 \((x, y)\) 在施 2. 操作后都可以到达 \((y, y)\),本质相同。
转二维平面路径为括号序列。
我们可以发现,如果设 O
为合法括号匹配,答案一定形如 O(O(...O(O
,这启发我们首先计数 (O
,再拼出答案。设卡特兰数的 ogf 是 \(C(z)\),并让 \(u\) 表示左括号的占位元,\(v\) 表示右括号的占位元,则形如 (O
的括号序列的组合类 \(\mathcal O_1\) 就能表为 \(v\left(1 + \sum_{i} C[i]u^iv^i\right) = v(1 + C(uv))\)。
设只讨论 1. 操作的操作序列的组合类是 \(\mathcal G\),则 \(\mathcal G = \text{SEQ}(\mathcal O_1)\),也就是
我们要算答案,首先要用 \(u, v\) 分别表示两个棋子的坐标,还要加一维 \(w\) 表示用的次数。设答案的组合类是 \(\mathcal F\),ogf 是 \(F(u, v, w)\)。
可以发现,若记一系列极长的 1. 操作为 \(C\),一次 2. 操作为 \(S\),则最终操作序列一定形如 CSCS...CSC
,并且最后一定停止在 \((a, b)\)。我们不妨考虑先构造 CS
,再加入一个 C
。为方便,记他们的组合类是 \(\text{CS}, \text{C}\)。
CS
就考虑一次 C
到定点 \((a, b)\),再冲到 \((b, b)\) 的贡献。这过程的方案数是 \(G[a, b]\),对 \(u, v\) 的贡献都是 \(b\),对 \(w\) 的贡献是 \(a + b + 1\)。也就是
C
的话类似,我们只需要考虑前半部分的贡献。这过程的方案数是 \(G[a, b]\),对 \(u\) 的贡献是 \(a\),\(v\) 的贡献是 \(b\),对 \(w\) 的贡献是 \(a + b\)。也就是
我们知道 \(\mathcal F= \text{SET}(\text{CS}) \times \text{C}\),也就是
答案就是 \([u^av^bw^n]F\)。
考虑分母 \(G\) 的第二维传进去了 \(uvw\),我们不妨先把他干掉。设 \(x = uv, y = v\),我们要的就是
到这一步,考虑对 \(\forall k\),有
我们知道,无论我们要的系数是什么,\((1 + C(xw^2))^{b - a + k}\) 都是一个多项式,因此我们想提取的 \(x^{a - k}\) 项一定形如 \(f_k(xw^2)^{a - k}\)。所以我们只需要提取出 \(w^{2(a - k)}\),之后对提取 \(x^{a - k}\) 项系数来说这多项式就等价于 \((1 + C(x))^{b - a + k}\) 了。这自然能证明如上的公式。
上面的结论想用到分式上还得讨论一下。上下两个部分得到的都是一个 \(xwf(xw^2)\) 的形式,我们需要提取 \(x^{a}\) 项的 \(w\),也就需要总共提取一个 \(w^{2a}\),并在 \(x\) 前留下一个 \(w^{-2}\)。具体看如下的推导:
不妨设置哑元 \(t\),令 \(t^k = [x^a]x^k (1 + C(x))^{b - a + k}\),显然有 \(t^0 = [x^a] (1 + C(x))^{b - a} = 1\)。有
最后的问题是如何算 \(t^k\),以及顺便验证 \(t^0 = 1\)。
考虑 \(t\) 是一个存在复合逆的函数的复合方程的形式,直觉告诉我们可以通过另类拉格朗日反演得到简洁的形式。
到这里就完全解决了本题。注意 \(t^{a + b} = [b = 0]\)。
总时间复杂度 \(O(n)\)。
以下是博客签名,与正文无关。
请按如下方式引用此页:
本文作者 joke3579,原文链接:https://www.cnblogs.com/joke3579/p/chitchat230317.html。
遵循 CC BY-NC-SA 4.0 协议。
请读者尽量不要在评论区发布与博客内文完全无关的评论,视情况可能删除。