20250821
T1
跑路
由于每次一定可以构造操作来反转最终路径上的一段区间,那么就相当于问路径上最少有多少 \(1\) 连通块,直接 dp 即可。
代码
#include <iostream>
#include <string.h>
using namespace std;
inline void Cmin(int &x, int y) { x = min(x, y); }
int n, m;
int a[1005][1005];
int f[1005][1005];
int main() {
freopen("run.in", "r", stdin);
freopen("run.out", "w", stdout);
memset(f, 63, sizeof f);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++)
cin >> a[i][j];
}
f[1][1] = a[1][1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (i != n) Cmin(f[i + 1][j], f[i][j] + (a[i + 1][j] == 1 && a[i][j] == 0));
if (j != m) Cmin(f[i][j + 1], f[i][j] + (a[i][j + 1] == 1 && a[i][j] == 0));
}
}
cout << f[n][m] << "\n";
return 0;
}
T2
回文子串个数最多
考虑每个回文中心,其回文半径两边的东西一定不同,如果改掉了两个之一就可以继续回文并给答案增加。那么两次二分求出两个回文半径。还要考虑如果改掉的在原本的回文半径当中,会减少答案,这个容易用二阶差分和前缀和统计。最后取个最优的修改方式即可。
代码
#include <iostream>
#include <valarray>
#define int long long
using namespace std;
const int B = 2333;
const int P = 1000000007;
int n;
string str;
int h1[100005], h2[100005];
int pw[100005];
int f[100005][30];
int H1(int l, int r) { return (h1[r] + P - h1[l - 1] * pw[r - l + 1] % P) % P; }
int H2(int l, int r) { return (h2[l] + P - h2[r + 1] * pw[r - l + 1] % P) % P; }
int p[100005], s[100005];
signed main() {
freopen("palinilap.in", "r", stdin);
freopen("palinilap.out", "w", stdout);
pw[0] = 1;
cin >> str; n = str.size(), str = ' ' + str;
for (int i = 1; i <= n; i++) pw[i] = pw[i - 1] * B % P;
for (int i = 1; i <= n; i++) h1[i] = (h1[i - 1] * B + str[i] - 'a' + 1) % P;
for (int i = n; i; i--) h2[i] = (h2[i + 1] * B + str[i] - 'a' + 1) % P;
int ans = 0;
for (int i = 1; i <= n; i++) {
int l = 1, r = min(n - i + 1, i), mid, tmp = 1, x = 1;
while (l <= r) {
mid = (l + r) >> 1;
if (H1(i, i + mid - 1) == H2(i - mid + 1, i)) tmp = mid, l = mid + 1;
else r = mid - 1;
}
ans += tmp;
p[i - tmp + 1]--, p[i]++, p[i] += tmp - 1, p[i + 1] -= tmp - 1;
s[i + tmp - 1]--, s[i]++, s[i] += tmp - 1, s[i - 1] -= tmp - 1;
if (i + tmp > n || i - tmp < 1) continue;
int tl = str[i - tmp] - 'a', tr = str[i + tmp] - 'a';
l = 2, r = min(i - tmp - 1, n - i - tmp) + 1;
while (l <= r) {
mid = (l + r) >> 1;
if (H1(i + tmp + 1, i + tmp + mid - 1) == H2(i - tmp - mid + 1, i - tmp - 1)) x = mid, l = mid + 1;
else r = mid - 1;
}
f[i - tmp][tr] += x, f[i + tmp][tl] += x;
}
for (int i = 1; i < n; i++) {
int l = 1, r = min(n - i, i), mid, tmp = 0, x = 1;
while (l <= r) {
mid = (l + r) >> 1;
if (H1(i + 1, i + mid) == H2(i - mid + 1, i)) tmp = mid, l = mid + 1;
else r = mid - 1;
}
ans += tmp;
p[i - tmp + 1]--, p[i + 1]++, p[i + 1] += tmp, p[i + 2] -= tmp;
s[i + tmp]--, s[i]++, s[i] += tmp, s[i - 1] -= tmp;
if (i + 1 + tmp > n || i - tmp < 1) continue;
int tl = str[i - tmp] - 'a', tr = str[i + tmp + 1] - 'a';
l = 2, r = min(i - tmp - 1, n - i - 1 - tmp) + 1;
while (l <= r) {
mid = (l + r) >> 1;
if (H1(i + tmp + 2, i + tmp + mid) == H2(i - tmp - mid + 1, i - tmp - 1)) x = mid, l = mid + 1;
else r = mid - 1;
}
f[i - tmp][tr] += x, f[i + tmp + 1][tl] += x;
}
for (int i = 1; i <= n; i++) p[i] += p[i - 1];
for (int i = 1; i <= n; i++) p[i] += p[i - 1];
for (int i = n; i; i--) s[i] += s[i + 1];
for (int i = n; i; i--) s[i] += s[i + 1];
int mx = 0;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < 26; j++) {
if (j != str[i] - 'a')
mx = max(mx, f[i][j] + p[i] + s[i]);
}
}
cout << ans + mx << "\n";
return 0;
}
T3
安全路径
考虑对路径建 AC 自动机,但是值域过大。我们观察建自动机的过程,发现最大的问题在于一个点要是没有某个儿子,就会把它的 fail 的那个儿子拿过来,这一步浪费了很多空间。我们考虑直接拿可持久化线段树维护这个东西,每次先把 fail 的线段树拿过来,然后再加入自己的所有儿子。这样就解决了空间的问题。接下来我们就可以在这上面跑最短路,状态为 \(f_{i, 0 / 1}\),第二维表示当前是否在匹配,若在匹配则第一维表示当前在自动机上的 \(i\) 号点,否则表示在原图的 \(i\) 点。但是直接跑最坏是 \(nm\) 的,因为自动机上的很多点可以对应同一个点,然后这同一个点可以有一堆出度,然后就爆了。考虑优化这个东西。由于 dijkstra 每次取出的 dist 递增,因此多次松弛同一条边没有意义。而根以外的每棵线段树上的每个叶子都对应一条出边,只要我们只访问每条出边一次复杂度就对了。因此每次松弛的时候 dfs 线段树,找到子树中的所有叶子并尝试松弛,然后标记这个结点被访问过,之后都不再访问。但是如果当前点是根开出来的,那由于每次在根的时候当前在哪个原图点不确定,因此不能直接 ban 掉子树。我们可以对每个原图点开一个 set,每次递归到根开出来的线段树节点 \([l, r]\) 就在当前原图点的 set 中找到区间 \([l, r]\) 的出边,尝试松弛并删掉它。这样就做完了。总共一个 \(\log\)。
代码
#include <iostream>
#include <algorithm>
#include <string.h>
#include <cassert>
#include <vector>
#include <queue>
#include <map>
#include <set>
#define int long long
using namespace std;
const int inf = 0x3f3f3f3f3f3f3f3f;
map<int, int> G[200005];
map<int, int> T[200005];
int n, m, K;
int head[200005], nxt[400005], to[400005], ew[400005], ecnt;
void add(int u, int v, int ww) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt, ew[ecnt] = ww; }
int cor[200005];
int ed[200005], fail[200005], ncnt;
int New() { return ++ncnt; }
string str;
void Insert(int len) {
int p = 0;
for (int i = 1; i <= len; i++) {
int t; cin >> t;
if (!T[p].count(t)) cor[T[p][t] = New()] = t;
p = T[p][t];
}
ed[p] = 1;
}
struct node {
int x, y, dis;
} tmp;
bool operator<(node a, node b) { return a.dis > b.dis; }
priority_queue<node> pq;
int dist[200005][2], X;
bool vis[200005][2];
set<int> st[200005];
int rt[200005];
struct Persistent_Segment_Tree {
struct tnode { int l, r, v; } T[10000005];
int ncnt;
void Insert(int &p, int q, int l, int r, int x, int y) {
T[p = ++ncnt] = T[q];
if (l == r) return T[p].v = y, void();
int mid = (l + r) >> 1;
if (x <= mid) Insert(T[p].l, T[q].l, l, mid, x, y);
else Insert(T[p].r, T[q].r, mid + 1, r, x, y);
}
int Query(int o, int l, int r, int x) {
if (!o) return 0;
if (l == r) return T[o].v;
int mid = (l + r) >> 1;
if (x <= mid) return Query(T[o].l, l, mid, x);
else return Query(T[o].r, mid + 1, r, x);
}
bool vis[10000005];
void dfs(int o, int l, int r, int x, int val, int fs) {
if (o <= X) {
for (set<int>::iterator it = st[x].lower_bound(l); it != st[x].end() && *it <= r; it = st[x].erase(it)) {
int v = *it, w = G[x][v], s = Query(rt[fs], 1, n, v);
if (s && !ed[s]) {
if (dist[s][1] > val + w)
pq.push((node) { s, 1, dist[s][1] = val + w });
} else if (!s) {
if (dist[v][0] > val + w)
pq.push((node) { v, 0, dist[v][0] = val + w });
}
}
return;
}
if (vis[o]) return;
vis[o] = 1;
if (l == r) {
int v = T[o].v, w;
if (G[x].count(cor[v])) {
w = G[x][cor[v]];
if (!ed[v]) {
if (dist[v][1] > val + w)
pq.push((node) { v, 1, dist[v][1] = val + w });
}
}
return;
}
int mid = (l + r) >> 1;
dfs(T[o].l, l, mid, x, val, fs);
dfs(T[o].r, mid + 1, r, x, val, fs);
}
} seg;
queue<int> q;
void Build() {
for (int i = 1; i <= n; i++) {
seg.Insert(rt[0], rt[0], 1, n, i, T[0][i]);
if (T[0][i]) q.push(T[0][i]);
}
X = seg.ncnt;
while (!q.empty()) {
int x = q.front();
q.pop();
ed[x] |= ed[fail[x]];
rt[x] = rt[fail[x]];
for (auto p : T[x]) {
q.push(p.second);
seg.Insert(rt[x], rt[x], 1, n, p.first, p.second);
fail[p.second] = seg.Query(rt[fail[x]], 1, n, p.first);
}
}
}
int dijkstra() {
memset(dist, 63, sizeof dist);
if (T[0][1] && !ed[T[0][1]]) pq.push((node) { T[0][1], 1, dist[T[0][1]][1] = 0 });
else if (!T[0][1]) pq.push((node) { 1, 0, dist[1][0] = 0 });
int ret = inf;
while (!pq.empty()) {
tmp = pq.top();
pq.pop();
int x = tmp.x, y = tmp.y;
if (vis[x][y])
continue;
vis[x][y] = 1;
if (y == 0) {
if (x == n) ret = min(ret, dist[x][y]);
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
if (T[0][v] && !ed[T[0][v]]) {
if (dist[T[0][v]][1] > dist[x][y] + ew[i])
pq.push((node) { T[0][v], 1, dist[T[0][v]][1] = dist[x][y] + ew[i] });
} else if (!T[0][v]) {
if (dist[v][0] > dist[x][y] + ew[i])
pq.push((node) { v, 0, dist[v][0] = dist[x][y] + ew[i] });
}
}
} else {
if (cor[x] == n) ret = min(ret, dist[x][y]);
seg.dfs(rt[x], 1, n, cor[x], dist[x][y], x);
}
}
return ret == inf ? -1 : ret;
}
signed main() {
freopen("safe.in", "r", stdin);
freopen("safe.out", "w", stdout);
cin >> n >> m >> K;
for (int i = 1; i <= m; i++) {
int u, v, ww;
cin >> u >> v >> ww;
add(u, v, ww);
G[u][v] = ww;
st[u].insert(v);
}
for (int i = 1, t; i <= K; i++) cin >> t, Insert(t);
Build();
cout << dijkstra() << "\n";
return 0;
}
T4
口头禅
按 height 从高往底加入并合并两边,每个询问就相当于问你在加入哪个 height 的时候区间 \([l, r]\) 的颜色第一次被全部合并到一块。那么只需要每次合并时启发式合并,记录下所有新产生的颜色段,然后全扔一块和询问做二维数点即可。复杂度瓶颈在 set 启发式合并的 \(2\log\)。
代码
#include <iostream>
#include <algorithm>
#include <set>
#define lowbit(x) ((x) & (-(x)))
using namespace std;
int n, q;
int a[800005], acnt, bel[800005];
int sa[800005], rk[800005], ht[800005];
pair<int, int> p[800005];
namespace SA {
int X[800005], Y[800005], *x = X, *y = Y;
int c[800005];
void SA(int *s, int n, int m) {
for (int i = 1; i <= n; i++) c[x[i] = s[i]]++;
for (int i = 1; i <= m; i++) c[i] += c[i - 1];
for (int i = n; i; i--) sa[c[x[i]]--] = i;
for (int d = 1; d <= n; d <<= 1) {
int tmp = 0;
for (int i = n - d + 1; i <= n; i++) y[++tmp] = i;
for (int i = 1; i <= n; i++) (sa[i] > d) ? (y[++tmp] = sa[i] - d) : 0;
for (int i = 0; i <= m; i++) c[i] = 0;
for (int i = 1; i <= n; i++) ++c[x[i]];
for (int i = 1; i <= m; i++) c[i] += c[i - 1];
for (int i = n; i; i--) sa[c[x[y[i]]]--] = y[i], y[i] = 0;
swap(x, y);
x[sa[1]] = tmp = 1;
for (int i = 2; i <= n; i++) x[sa[i]] = (tmp += (y[sa[i]] != y[sa[i - 1]] || y[sa[i] + d] != y[sa[i - 1] + d]));
if (tmp == n) break;
m = tmp;
}
s[n + 1] = -1;
for (int i = 1; i <= n; i++) rk[sa[i]] = i;
for (int i = 1, k = 0; i <= n; i++) {
k -= (k != 0);
while (s[i + k] == s[sa[rk[i] - 1] + k]) ++k;
ht[rk[i]] = k;
}
for (int i = 1; i <= n; i++) p[i] = make_pair(ht[i], i);
}
}
set<pair<int, int> > st[800005];
int qcnt, f[800005];
struct Query { int l, r, v; } qs[1000005];
bool chk(int l1, int r1, int l2, int r2) {
if (l1 > l2) swap(l1, l2), swap(r1, r2);
return r1 >= l2 - 1;
}
void Merge(int x, int y, int v) {
if (st[x].size() > st[y].size()) swap(st[x], st[y]);
set<pair<int, int> >::iterator it;
for (auto p : st[x]) {
int l = p.first, r = p.second;
it = st[y].lower_bound({ l, -1 });
if (it != st[y].begin() && prev(it) -> second >= l - 1) --it;
int tl = l, tr = r;
while (it != st[y].end()) {
int ll = it -> first, rr = it -> second;
if (!chk(ll, rr, l, r)) break;
tl = min(ll, tl), tr = max(rr, tr);
it = st[y].erase(it);
}
if (tl != l || tr != r) qs[++qcnt] = (Query) { tl, tr, v };
st[y].insert({ tl, tr });
}
st[x].clear();
}
struct BIT {
int bit[800005];
void add(int x, int y) { for (; x; x -= lowbit(x)) bit[x] = max(bit[x], y); }
int query(int x) {
int ret = 0;
for (; x <= acnt; x += lowbit(x)) ret = max(ret, bit[x]);
return ret;
}
} bit;
int ans[200005];
int getf(int x) { return (f[x] == x ? x : (f[x] = getf(f[x]))); }
int main() {
freopen("phrase.in", "r", stdin);
freopen("phrase.out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> q;
for (int i = 1; i <= n; i++) {
string str; cin >> str;
for (auto v : str) a[++acnt] = v - '0' + 1, bel[acnt] = i;
a[++acnt] = i + 3;
}
SA::SA(a, acnt, n + 3);
sort(p + 2, p + acnt + 1, greater<pair<int, int> >());
for (int i = 1; i <= acnt; i++) f[i] = i, st[i].insert({ bel[sa[i]], bel[sa[i]] });
for (int i = 2; i <= acnt; i++) Merge(p[i].second - 1, getf(p[i].second), p[i].first), f[p[i].second - 1] = p[i].second;
for (int i = 1; i <= q; i++) {
int l, r;
cin >> l >> r;
qs[++qcnt] = (Query) { l, r, -i };
}
sort(qs + 1, qs + qcnt + 1, [](Query x, Query y) { return (x.l == y.l ? (x.r == y.r ? (x.v > y.v) : (x.r > y.r)) : (x.l < y.l)); });
for (int i = 1; i <= qcnt; i++) {
if (qs[i].v < 0) ans[-qs[i].v] = bit.query(qs[i].r);
else bit.add(qs[i].r, qs[i].v);
}
for (int i = 1; i <= q; i++) cout << ans[i] << "\n";
return 0;
}
字符集很大的 AC 自动机处理:信息复用。
T4 经典套路。