20250905
T1
模糊匹配
对于一个串 \(A\),设其为问号的位置集合为 \(S_A\),尝试算它和前面有多少匹配。枚举它和前面的 \(B\) 的 \(S_A \cup S_B\),哈希剩下的位,然后只需要知道有多少 \(S\) 为 \(S_B\) 的串去掉 \(S_A \cup S_B\) 之后和 \(A\) 匹配。于是对每种 \(S\) 的每个超集开一个哈希表存哈希值,一共是 \(64 \times 64\) 个哈希表。总时间复杂度 \(\mathcal{O}(nm2^m)\)。精细实现可以去掉一个 \(m\)。
也可以设 \(S_A\) 表示在 \(A\) 中为问号,在 \(B\) 中不为问号的集合,然后也是枚举 \(S_A \cup S_B\),相当于剩下的串就把问号也哈希起来一块匹配。这样只需要开 \(64\) 个哈希表。
也可以容斥,我们考虑正难则反。钦定一些位不同,那么不同的位必须都不是问号,且字母也不同。都不是问号是好做的,但是字母不同不好做。再考虑正难则反,我们钦定一些位置字母相同。那么这样就可以做了,只需要对钦定相同的位置哈希,剩下的只需要保证不是问号。容斥系数就是 \((-1)^{|S| + |T|}\),复杂度 \(\mathcal{O}(nm3^m)\)。
代码
#include <iostream>
#include <unordered_map>
#include <string.h>
#define int long long
using namespace std;
const int P = 3355779988664422213;
const __int128 B = 5551;
int n, m;
int ans;
unordered_map<int, int> mp[65][65];
signed main() {
freopen("match.in", "r", stdin);
freopen("match.out", "w", stdout);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
string str;
cin >> str;
int S = 0;
for (int j = 0; j < m; j++) if (str[j] == '?') S |= (1 << j);
for (int T = 0; T < (1 << m); T++) {
long long h = 0;
for (int j = 0; j < m; j++) if (!((S | T) & (1 << j))) h = (h * B + str[j] - 'a' + 1) % P;
ans += mp[T][S | T][h];
}
for (int T = 0; T < (1 << m); T++) {
if ((S & T) != S) continue;
long long h = 0;
for (int j = 0; j < m; j++) if (!(T & (1 << j))) h = (h * B + str[j] - 'a' + 1) % P;
++mp[S][T][h];
}
}
cout << ans << "\n";
return 0;
}
T2
岛屿旅行
设 \(f_{i, j, k}\) 表示从起点坐了 \(k\) 次飞机到 \((i, j)\) 的最短路。每次转移需要枚举下一个岛去哪,复杂度就爆了。如果直接上分步转移,确实也可以做到三次方,但是常数飞起。我写了,过不去一点。
但是注意到这张图实际上是关于 \(k\) 分层的。也就是我们 \(f_{k, i, j}\) 只会从 \(f_{k - 1, i, j}\) 转移。转移的代价是曼哈顿距离,显然可以拆四部分分别前缀和优化转移。但是这样可能会从自己转移到自己,于是再记最小值来源和次小值即可。
代码
#include <iostream>
#include <string.h>
#include <queue>
#include <array>
using namespace std;
const int inf = 0x3f3f3f3f;
int n, m, X, Y;
string str[305];
int dsu[90005], id[305][305], ncnt;
int getf(int x) { return dsu[x] == x ? x : (dsu[x] = getf(dsu[x])); }
void M(int x, int y) { x = getf(x), y = getf(y); (x != y) ? (dsu[x] = y) : 0; }
int bel[305][305];
int dist[305][305][305];
array<int, 3> mn[305][305];
array<int, 3> _min(array<int, 3> a, array<int, 3> b) {
if (a[0] > b[0]) swap(a, b);
if (a[1] == b[1]) a[2] = min(a[2], b[2]);
else a[2] = min(a[2], b[0]);
return a;
}
int _mn[90005];
void bfs() {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++)
if (bel[i][j] == bel[X][Y]) dist[0][i][j] = 0;
}
for (int k = 1; k <= 300; k++) {
memset(mn, 63, sizeof mn);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
mn[i][j] = _min(mn[i - 1][j], mn[i][j - 1]);
if (str[i][j] == '0') continue;
dist[k][i][j] = min(dist[k][i][j], i + j + (bel[i][j] == mn[i][j][1] ? mn[i][j][2] : mn[i][j][0]));
mn[i][j] = _min(mn[i][j], { dist[k - 1][i][j] - i - j, bel[i][j], inf });
}
}
memset(mn, 63, sizeof mn);
for (int i = n; i; i--) {
for (int j = 1; j <= m; j++) {
mn[i][j] = _min(mn[i + 1][j], mn[i][j - 1]);
if (str[i][j] == '0') continue;
dist[k][i][j] = min(dist[k][i][j], -i + j + (bel[i][j] == mn[i][j][1] ? mn[i][j][2] : mn[i][j][0]));
mn[i][j] = _min(mn[i][j], { dist[k - 1][i][j] + i - j, bel[i][j], inf });
}
}
memset(mn, 63, sizeof mn);
for (int i = 1; i <= n; i++) {
for (int j = m; j; j--) {
mn[i][j] = _min(mn[i - 1][j], mn[i][j + 1]);
if (str[i][j] == '0') continue;
dist[k][i][j] = min(dist[k][i][j], i - j + (bel[i][j] == mn[i][j][1] ? mn[i][j][2] : mn[i][j][0]));
mn[i][j] = _min(mn[i][j], { dist[k - 1][i][j] - i + j, bel[i][j], inf });
}
}
memset(mn, 63, sizeof mn);
for (int i = n; i; i--) {
for (int j = m; j; j--) {
mn[i][j] = _min(mn[i + 1][j], mn[i][j + 1]);
if (str[i][j] == '0') continue;
dist[k][i][j] = min(dist[k][i][j], -i - j + (bel[i][j] == mn[i][j][1] ? mn[i][j][2] : mn[i][j][0]));
mn[i][j] = _min(mn[i][j], { dist[k - 1][i][j] + i + j, bel[i][j], inf });
}
}
memset(_mn, 63, sizeof _mn);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (str[i][j] == '1')
_mn[getf(id[i][j])] = min(_mn[getf(id[i][j])], dist[k][i][j]);
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (str[i][j] == '1')
dist[k][i][j] = _mn[getf(id[i][j])];
}
}
}
}
int to[305][305][4];
int dx[4] = { 1, 0, -1, 0 };
int dy[4] = { 0, 1, 0, -1 };
struct node { int x, y, z, w, ww; };
deque<node> q;
int _d[305][305][2];
bool _vis[305][305][2];
void bfs2() {
_d[X][Y][0] = 0;
q.push_front((node) { X, Y, 0, 0, 0 });
while (!q.empty()) {
node tmp = q.front(); q.pop_front();
int x = tmp.x, y = tmp.y, z = tmp.z;
if (_vis[x][y][z]) continue;
_vis[x][y][z] = 1;
for (int i = 0; i < 4; i++) {
int tx = x + dx[i], ty = y + dy[i];
if (!tx || !ty || tx > n || ty > m) continue;
int ew = (!z && (bel[x][y] != bel[X][Y] || bel[tx][ty] != bel[X][Y])) || (z && bel[x][y] != bel[tx][ty]);
auto chkupd = [&](int tz) {
if (ew) {
if (_d[x][y][z] + ew < _d[tx][ty][tz]) {
_d[tx][ty][tz] = _d[x][y][z] + ew;
q.push_back((node) { tx, ty, tz, 0, 0 });
}
} else {
if (_d[x][y][z] + ew < _d[tx][ty][tz]) {
_d[tx][ty][tz] = _d[x][y][z] + ew;
q.push_front((node) { tx, ty, tz, 0, 0 });
}
}
};
if (str[x][y] == '0' && str[tx][ty] == '0') { if (!z) chkupd(0); }
else if (str[x][y] == '1' && str[tx][ty] == '1') chkupd(z);
else if (str[x][y] == '1' && str[tx][ty] == '0') { if (!z) chkupd(0); }
else if (!z) chkupd(0), chkupd(1);
}
}
}
int main() {
freopen("island.in", "r", stdin);
freopen("island.out", "w", stdout);
memset(_d, 63, sizeof _d);
memset(dist, 63, sizeof dist);
cin >> n >> m >> X >> Y;
for (int i = 1; i <= n; i++) cin >> str[i], str[i] = ' ' + str[i];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++)
id[i][j] = ++ncnt, dsu[ncnt] = ncnt;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
i != 1 && str[i][j] == '1' && str[i][j] == str[i - 1][j] ? M(id[i][j], id[i - 1][j]) : void();
j != 1 && str[i][j] == '1' && str[i][j] == str[i][j - 1] ? M(id[i][j], id[i][j - 1]) : void();
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (str[i][j] == '1') to[i][j][0] = to[i][j][1] = to[i][j][2] = to[i][j][3] = bel[i][j] = getf(id[i][j]);
else to[i][j][2] = to[i - 1][j][2], to[i][j][3] = to[i][j - 1][3];
}
}
for (int i = n; i; i--) {
for (int j = m; j; j--) {
if (str[i][j] == '0') to[i][j][0] = to[i + 1][j][0], to[i][j][1] = to[i][j + 1][1];
}
}
bfs();
bfs2();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (str[i][j] == '0' || bel[i][j] == bel[X][Y]) cout << 0 << " ";
else {
for (int k = 300; k >= -1; k--) {
if (k == -1 || dist[k][i][j] <= _d[i][j][1]) {
cout << k << " ";
break;
}
}
}
}
cout << "\n";
}
return 0;
}
T3
树状数组
树状数组的原理是,每个位置 \(i\) 存 \([i - lowbit(i) + 1, i]\) 这些位置的和。显然我们只关心每个位置管辖的区间长度,而本质不同的区间长度只有 \(\log n\) 种。因此对每个长度统计。设 \(cnt_p\) 为有多少个数的 \(lowbit\) 为 \(p\),我们有 \(ans =\frac1{n^m(S + 1)^m}\sum\limits_{p = 2^i}cnt_p\sum\limits_{x = 0}^m\binom{m}{x}len^x(n - len)^{m - x}(S + 1)^{m - x}\sum\limits_{s = 0}^{xS}s^kg_{x, s}\),其中 \(g_{x, s}\) 表示选出 \(x\) 个 \([0, S]\) 加起来和为 \(s\) 的方案数。
考虑直接把 \(k\) 次幂拆了,大力交换求和号,变成 \(\frac1{n^m(S + 1)^m}\sum\limits_{p = 2^i}cnt_p\sum\limits_{i = 0}^k{k\brace i}i!\sum\limits_{x = 0}^m\binom{m}{x}len^x(n - len)^{m - x}(S + 1)^{m - x}\sum\limits_{s = 0}^{xS}\binom{s}{i}g_{x, s}\)。先考虑最后那个 \(\sum\limits_{s = 0}^{xS}\binom{s}{i}g_{x, s}\),发现这个可以组合意义,视为你有 \(x\) 次操作,每次操作往一个序列末尾加 \([0, S]\) 个球,加完之后在其中选择一些染黑,到最后一共有 \(i\) 个黑球的方案数。显然如果只有一次操作,那么有 \(i\) 个黑球的方案数通过组合数列求和容易化成 \(\binom{S + 1}{i + 1}\)。由于 \(i\) 很小,多次操作的情况也可以直接倍增卷积合并。
接下来考虑原式。我们要求 \(\sum\limits_{x = 0}^m\binom{m}{x}len^x(n - len)^{m - x}(S + 1)^{m - x}\sum\limits_{s = 0}^{xS}\binom{s}{i}g_{x, s}\)。根据刚才的经验,我们还是考虑对这个东西编组合意义。发现可以视为有 \(m\) 次操作,每次可以选择要放球或不放球,如果不放球会有 \((n - len)(S + 1)\) 的贡献,选择放球就会有 \(len\) 的贡献,同时可以选择放 \([0, S]\) 个球,并选择其中的一些染黑,然后最后要求一共放了 \(i\) 个黑球的方案数。显然这个也可以用上面那个倍增做,只需要把单次操作的方案全乘 \(len\),再把 \(0\) 的位置加上 \((n - len)(S + 1)\) 即可。
另一种做法,我们考虑先求出一次操作的每个次方的期望,合并可以根据二项式定理的展开通过二项卷积合并左右。而对于一次操作,有 \(\frac{len}{n}\) 的概率加一个数,否则加 \(0\)。根据期望的线性性拆开,只需要求加一个数的时候各次方的期望。显然这是自然数幂和,套个拉插过来即可。
代码
#include <iostream>
#include <string.h>
#include <vector>
#define int long long
using namespace std;
const int P = 1000000007;
int n, m, s, K;
int C[305], S[305][305], fac[305];
using poly = vector<int>;
poly operator*(poly a, poly b) {
poly c; c.resize(K + 1);
for (int i = 0; i <= K; i++) {
c[i] = 0;
for (int j = 0; j <= i; j++) c[i] += a[j] * b[i - j] % P;
c[i] %= P;
}
return c;
}
poly qpow(poly x, int y) {
poly ret = x; --y;
while (y) {
if (y & 1)
ret = ret * x;
y >>= 1;
x = x * x;
}
return ret;
}
int qpow(int x, int y) {
int ret = 1;
while (y) {
if (y & 1)
ret = ret * x % P;
y >>= 1;
x = x * x % P;
}
return ret;
}
poly f0;
signed main() {
freopen("fenwick.in", "r", stdin);
freopen("fenwick.out", "w", stdout);
cin >> n >> s >> m >> K;
S[0][0] = C[0] = fac[0] = 1;
for (int i = 1; i <= K + 1; i++) fac[i] = fac[i - 1] * i % P;
for (int i = 1; i <= K; i++) {
for (int j = 1; j <= i; j++)
S[i][j] = (S[i - 1][j - 1] + S[i - 1][j] * j) % P;
}
for (int i = 1; i <= K + 1; i++) C[i] = C[i - 1] * (s + 2 - i) % P * qpow(i, P - 2) % P;
int ans = 0;
for (int i = 1; i <= n; i <<= 1) {
int cnt = n / i - n / (i << 1);
f0.clear(); f0.resize(K + 1);
for (int j = 0; j <= K; j++) f0[j] = i * C[j + 1] % P;
f0[0] = (f0[0] + (n - i) * (s + 1) % P) % P;
poly tmp = qpow(f0, m);
for (int j = 0; j <= K; j++) ans += cnt * S[K][j] % P * fac[j] % P * tmp[j] % P;
ans %= P;
}
cout << ans % P * qpow(qpow(n, P - 2), m) % P * qpow(qpow(s + 1, P - 2), m) % P << "\n";
return 0;
}
T4
字符串
先在每个字符串后面加上一个特殊字符,然后对所有字符串建 trie 树来表示 LCP。则任意合法方案为一个 dfs 序。那么每条限制就相当于钦定某个叶子必须在 dfs 序中另一个的恰好前面。相当于对于两个叶子到根链的点上,要求某个点必须是子树 dfs 序的开头或结尾,对于两个的 LCA,则要求两棵子树以相邻顺序 dfs。链表维护所有限制,直接做就是平方。
但是注意到只有一个儿子的点对答案没有影响,直接删掉。那么删完之后观察剩下的东西,发现深度不超过根号了(考虑最坏情况,一条链每个点各挂一个儿子,此时由于 \(\sum dep_{leaf}\) 有保障,因此最大深度是根号)。于是直接暴力跳 LCA 检查路径并更新即可。
代码
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int n, q;
long long ans;
int X[100005], Y[100005];
string str[40005];
int son[300005][27], sc[300005], ncnt;
int fa[300005], ep[300005], cor[300005];
int Insert(string S, int id) {
int p = 0;
for (int i = 0; i < (int)S.size(); i++) {
int t = S[i] - 'a';
if (!son[p][t]) fa[son[p][t] = ++ncnt] = p, ++sc[p];
p = son[p][t];
}
cor[p] = id;
return p;
}
int f[300005];
int getf(int x) { return f[x] == x ? x : (f[x] = getf(f[x])); }
vector<int> G[300005];
int dep[300005];
void _dfs(int x) {
for (int i = 0; i < 27; i++) if (son[x][i]) dep[son[x][i]] = dep[x] + 1, _dfs(son[x][i]);
ans += 1ll * dep[x] * dep[x] * max(0, sc[x] - 1);
}
void dfs(int x) { sc[x] = G[x].size(); dep[x] = dep[fa[x]] + 1; for (auto v : G[x]) dfs(v); }
int pre[300005], nxt[300005], sz[300005], head[300005], tail[300005];
bool th[300005], tt[300005];
bool chk(int x, int y, int &z) {
int tx = x, ty = y;
while (tx ^ ty) (dep[tx] < dep[ty]) ? (ty = fa[ty]) : (tx = fa[tx]);
z = tx;
while (fa[x] ^ z) {
int fx = fa[x];
if ((tail[fx] && tail[fx] != x) || nxt[x] || (th[x] && sz[x] != sc[fx])) return 0;
x = fa[x];
}
while (fa[y] ^ z) {
int fy = fa[y];
if ((head[fy] && head[fy] != y) || pre[y] || (tt[y] && sz[y] != sc[fy])) return 0;
y = fa[y];
}
if (tail[z] == x || head[z] == y || (nxt[x] && nxt[x] != y) || (pre[y] && pre[y] != x) || (th[x] && tt[y] && sz[x] + sz[y] != sc[z])) return 0;
while (y) { y = nxt[y]; if (y == x) return 0; }
return 1;
}
void upd(int x, int y, int z) {
int tx, ty;
while (fa[x] ^ z) {
int fx = fa[x]; tx = x;
tail[fx] = x;
while (tx) tt[tx] = 1, tx = pre[tx];
x = fa[x];
}
while (fa[y] ^ z) {
int fy = fa[y]; ty = y;
head[fy] = y;
while (ty) th[ty] = 1, ty = nxt[ty];
y = fa[y];
}
nxt[x] = y, pre[y] = x;
int tmp = sz[x] + sz[y];
tx = x; while (tx) sz[tx] = tmp, tt[tx] |= tt[y], tx = pre[tx];
ty = y; while (ty) sz[ty] = tmp, th[ty] |= th[x], ty = nxt[ty];
}
bool vis[300005];
void dfs_(int x) {
if (cor[x]) return cout << cor[x] << " ", void();
if (head[x]) for (int i = head[x]; i; i = nxt[i]) dfs_(i), vis[i] = 1;
for (auto v : G[x]) if (!vis[v] && !tt[v] && !pre[v]) for (int i = v; i; i = nxt[i]) dfs_(i), vis[i] = 1;
for (auto v : G[x]) if (!vis[v] && !pre[v]) for (int i = v; i; i = nxt[i]) dfs_(i), vis[i] = 1;
}
vector<int> ok;
int main() {
freopen("reorder.in", "r", stdin);
freopen("reorder.out", "w", stdout);
cin >> n >> q;
for (int i = 1; i <= n; i++) cin >> str[i], ep[i] = Insert(str[i] + '{', i);
for (int i = 1; i <= q; i++) cin >> X[i] >> Y[i];
for (int i = 1; i <= ncnt; i++) f[i] = i;
for (int i = 1; i <= ncnt; i++) {
sz[i] = 1;
if (sc[i] == 1) f[i] = getf(fa[i]);
else {
fa[i] = getf(fa[i]);
G[fa[i]].emplace_back(i);
}
}
_dfs(0);
dfs(0);
cout << ans << "\n";
for (int i = q, z; i; i--) if (chk(ep[X[i]], ep[Y[i]], z)) upd(ep[X[i]], ep[Y[i]], z), ok.emplace_back(i);
reverse(ok.begin(), ok.end());
cout << ok.size() << " "; for (auto v : ok) cout << v << " ";
cout << "\n";
dfs_(0);
cout << "\n";
return 0;
}
问题很大,总结一下。
T1 很快会的做法 MLE,此时没有选择想一个更优的,尽管其实有点头绪,但决定先过 T2。但其实时间并不少,应该先想 T1。可能是太想比对面人优越了,但其实没必要。
T2 假了好几次,最后比赛快结束才真了,但是常数过大被卡了。因此没有时间重开 T1,也没有时间开 T3。场中其实心态不是很好,因为一直知道 T1 还在 MLE。所以其实还是应该先把 T1 过了,这样之后心态可能会好一点。
T2,分层图。
T3。

浙公网安备 33010602011771号