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 经典套路。

posted @ 2025-08-25 13:32  forgotmyhandle  阅读(6)  评论(0)    收藏  举报