竞赛讨论区 > 【题解】"蔚来杯"2022牛客暑期多校训练营3
头像
王清楚
编辑于 2022-07-29 16:31
+ 关注

【题解】"蔚来杯"2022牛客暑期多校训练营3

A

只需要预处理前后缀 ,然后枚举关键点 ,用前后缀的 再求一次 即可求出剩余关键点的 ,然后比较大小贡献答案。

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 9;
int n;
int read() {
    int f = 0, x = 0;
    char ch = getchar();

    while (!isdigit(ch)) {
        f |= (ch == '-');
        ch = getchar();
    }

    while (isdigit(ch)) {
        x = x * 10 + ch - '0';
        ch = getchar();
    }

    return f ? -x : x;
}
struct Tree {
    int fa[N][20], dep[N], a[N];
    vector<int>e[N];
    void dfs(int x, int f) {
        fa[x][0] = f, dep[x] = dep[f] + 1;

        for (int i = 1; i <= 17; i++)
            fa[x][i] = fa[fa[x][i - 1]][i - 1];

        for (int to : e[x])
            if (to != f)
                dfs(to, x);
    }
    int LCA(int x, int y) {
        if (x == 0 || y == 0)
            return x + y;

        if (dep[x] < dep[y])
            swap(x, y);

        for (int i = 17; i >= 0; i--)
            if (dep[x] - (1 << i) >= dep[y])
                x = fa[x][i];

        if (x == y)
            return x;

        for (int i = 17; i >= 0; i--)
            if (fa[x][i]^fa[y][i])
                x = fa[x][i], y = fa[y][i];

        return fa[x][0];
    }
    void init() {
        for (int i = 1; i <= n; i++)
            a[i] = read();

        for (int i = 2; i <= n; i++) {
            int x = read();
            e[i].push_back(x);
            e[x].push_back(i);
        }

        dfs(1, 0);
    }
} T1, T2;
int a[N], L[N][2], R[N][2], k;
int main() {
    n = read(), k = read();

    for (int i = 1; i <= k; i++)
        a[i] = read();

    T1.init(), T2.init();

    for (int i = 1; i <= k; i++) {
        L[i][0] = T1.LCA(L[i - 1][0], a[i]);
        L[i][1] = T2.LCA(L[i - 1][1], a[i]);
    }

    for (int i = k; i; i--) {
        R[i][0] = T1.LCA(R[i + 1][0], a[i]);
        R[i][1] = T2.LCA(R[i + 1][1], a[i]);
    }

    int ans = 0;

    for (int i = 1; i <= k; i++) {
        int x0 = T1.LCA(L[i - 1][0], R[i + 1][0]);
        int x1 = T2.LCA(L[i - 1][1], R[i + 1][1]);

        if (T1.a[x0] > T2.a[x1])
            ans++;
    }

    cout << ans << endl;
    return 0;
}

B

首先有一个做法就是,建图然后跑最小费用最大流。显然过不了。

模拟费用流的过程,每次都是把一些人从某个城市移动到另一个城市。

一个人 若从城市 移动到城市 ,代价就是 。因为增广路只会经过每个点至多一次,所以如果有多个人可以从城市 移动到城市 ,肯定会优先考虑代价最小的人。

那么不妨只建立 个点,城市 之间的边就是所有在城市 的人移动到城市 的代价。 显然边的数量还是 级别,但是对于重边只取代价最小的边,所以开 个小根堆来维护所有的边。这样就可以放心跑 了!但是注意每跑一次就要重新更新边集,初始把所有人放在城市

因为会跑 ,并且每次会移动至多 个人所在城市,所以时间复杂度最坏是

最坏是

#include <bits/stdc++.h>
#define int long long
#define w first
#define se second
#define mk make_pair
using namespace std;
const int inf = 1e9;
const int N = 1e5 + 9;
int read() {
    int f = 0, x = 0;
    char ch = getchar();
    while (!isdigit(ch)) {
        f |= (ch == '-');
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return f ? -x : x;
}
int n, k;
int dis[12], vis[12], fa[12];
int a[12], c[N][12], in[N];
priority_queue<pair<int, int>> e[12][12];
int SPFA(int rt) {
    queue<int> q;
    q.push(rt);
    for (int i = 1; i <= 10; i++)
        dis[i] = inf, vis[i] = 0, fa[i] = 0;
    dis[rt] = 0, vis[rt] = 1;
    while (q.size()) {
        int x = q.front();
        q.pop();
        vis[x] = 0;
        for (int y = 1; y < rt; y++) {
            if (!e[x][y].size())
                continue;
            int d = -e[x][y].top().w;
            if (dis[y] > dis[x] + d) {
                dis[y] = dis[x] + d;
                fa[y] = x;
                if (vis[y])
                    continue;
                vis[y] = 1;
                q.push(y);
            }
        }
    }
    vector<int>A;
    int x = 1;
    while (x != rt) {
        A.push_back(e[fa[x]][x].top().se);
        in[e[fa[x]][x].top().se] = fa[x];
        x = fa[x];
    }
    for (auto x : A)
        for (int i = 1; i <= rt; i++)
            if (i != x)
                e[i][in[x]].push(mk(-c[x][i] + c[x][in[x]], x));
    return dis[1];
}
signed main() {
    n = read(), k = read();
    for (int i = 1; i <= k; i++)
        a[i] = read();
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= k; ++j)
            c[i][j] = read();
    int ans = 0;
    for (int i = 1; i <= n; i++)
        ans += c[i][1], in[i] = 1;
    for (int j = 2; j <= k; ++j) {
        for (int i = 1; i <= n; i++)
            e[j][in[i]].push(mk(c[i][in[i]] - c[i][j], i));
        int k = a[j];
        while (k--) {
            for (int u = 1; u <= j; ++u)
                for (int v = 1; v <= j; ++v)
                    while (e[u][v].size() && in[e[u][v].top().se] != v)
                        e[u][v].pop();
            ans += SPFA(j);
        }
    }
    printf("%lld\n", ans);
    return 0;
}

C

若字符串 在字符串 前面,那么需要满足 。直接 sort 就可以做到 的复杂度。

不妨将 画出来观察(这里假设

aaabbbbbb
bbbbbbaaa

这里用 a 来代替 中字符, b 来代替 中字符。同时用 表示字符串 开头的后缀。

我们发现,若 不是 的前缀,我们可以直接比较 的大小。这里可以用 比较 序来实现,

否则,把 分成三部分,其中第一部分为开头长为 的字符串,第三部分为末尾长为 的字符串,剩下字符为第二部分。那么就可以依次比较一、二、三部分的字符串从而得到 的大小关系。

由于 的前缀,所以第一部分是相同的。而第二部分就是 本身比较大小。这使我们想到 exkmp ,因为 exkmp 能在线性时间内求出字符串 的每一个后缀与

若第二部分也相同,我们比较第三部分,也就是 比较大小。由于 的前缀,所以等价于 比较大小。也能用 exkmp 快速找到 并比较下一位字符。

至此,时间复杂度 ,但由于 常数较大,需要进行一些写法上的优化。

#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 9;
void chmx(int &x, int y) {
    (x < y) &&(x = y);
}
void chmn(int &x, int y) {
    (x > y) &&(x = y);
}
int read() {
    int f = 0, x = 0;
    char ch = getchar();
    while (!isdigit(ch)) {
        f |= (ch == '-');
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return f ? -x : x;
}
vector<int>z[N];
string s[N];
void get(vector<int> &z, string &s) {
    int n = s.size() - 1;
    z.resize(n + 1);
    z[1] = n;
    for (int i = 2, l = 0, r = 0; i <= n; i++) {
        if (i <= r)
            z[i] = min(z[i - l + 1], r - i + 1);
        while (i + z[i] <= n && s[i + z[i]] == s[z[i] + 1])
            ++z[i];
        if (i + z[i] - 1 > r)
            l = i, r = i + z[i] - 1;
    }
}
int ed[N], dfn[N * 10], sz[N * 10], b[N];
struct trie {
    int son[N * 10][5];
    int idx, id;
    trie() {
        idx = 1;
    }
    int add(string &s) {
        int x = 1, n = s.size() - 1;
        for(int i = 1;i <= n; i++) {
            int nt = s[i] - '0';
            if (!son[x][nt])
                son[x][nt] = ++idx;
            x = son[x][nt];
        }
        return x;
    }
    void dfs(int x) {
        dfn[x] = ++id;
        sz[x] = 1;
        for(int i = 0;i <= 4; i++)if (son[x][i]) {
            dfs(son[x][i]);
            sz[x] += sz[son[x][i]];
        }
    }
} T;
int a[N], len[N];
bool in(int x, int y) {
    x = ed[x];
    y = ed[y];
    return dfn[x] <= dfn[y] && dfn[y] < dfn[x] + sz[x] ||
           dfn[y] <= dfn[x] && dfn[x] < dfn[y] + sz[y];
}
bool cmp(int x, int y) {
    if (!in(x, y))
        return dfn[ed[x]] < dfn[ed[y]];
    if (ed[x] == ed[y])
        return 0;
    if (len[x] < len[y]) {
        int k = z[y][len[x] + 1];
        if (k == len[y] - len[x]) {
            k = z[y][len[y] - len[x] + 1];
            if (k == len[x])
                return 0;
            return s[y][len[y] - len[x] + k + 1] < s[x][k + 1];
        }
        return s[y][k + 1] < s[y][len[x] + k + 1];
    } else {
        swap(x, y);
        int k = z[y][len[x] + 1];
        if (k == len[y] - len[x]) {
            k = z[y][len[y] - len[x] + 1];
            if (k == len[x])
                return 0;
            return s[y][len[y] - len[x] + k + 1] > s[x][k + 1];
        }
        return s[y][k + 1] > s[y][len[x] + k + 1];
    }
}
signed main() {
    int n = read();
    for(int i = 1;i <= n; i++) {
        cin >> s[i];
        s[i] = "0" + s[i];
        len[i] = s[i].size() - 1;
        get(z[i], s[i]);
        ed[i] = T.add(s[i]);
        a[i] = i;
    }
    T.dfs(1);
    sort(a + 1, a + 1 + n, cmp);
    for(int i = 1;i <= n; i++) for(int j = 1;j <= len[a[i]]; j++)putchar(s[a[i]][j]);
    return 0;
}

D

对于 的情况,设 表示从 走到 的期望步数。最终答案就是 路径上每条边的期望步数之和。那么有转移式子:
移到左边去
可知, 最终等于 。因为子树内每一条边会被计算两次, 的边会被计算一次。

考虑一条单向边 的贡献,对于子树内的 没有影响。而对于 ,相比原来的值减少了 ,也就是减少了 ,并且对于所有是 祖先的 值都减少了 。因为这等价于直接砍掉 这棵子树(因为到不了)。

换言之,一个点 对祖先 有贡献当且仅当它们之间不存在单向边。这个贡献可以组合数来求。并且由于产生贡献的距离连续,在 dfs 的时候维护区间左右端点然后区间求和。

具体的,对于距离为 的两个点 对于 的贡献为:

#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int N = 1e6 + 9;
const int p = 998244353;
bool vis[N];
int n, k, s, fa[N], dep[N], cnt, D;
int  a[N], ans;
vector <int> e[N];
int read() {
    int f = 0, x = 0;
    char ch = getchar();
    while (!isdigit(ch)) {
        f |= (ch == '-');
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return f ? -x : x;
}
int inv(int x, int base = p - 2) {
    int res = 1;
    while (base) {
        if (base & 1)
            res = 1ll * res * x % p;

        x = 1ll * x * x % p;
        base >>= 1;
    }
    return res;
}
void dfs(int x, int f) {
    fa[x] = f, dep[x] = dep[f] + 1;
    for (auto v : e[x])
        if (v != f)
            dfs(v, x);
}
int  calc(int x, int y) {
    return y ? (a[x + y - 1] + p - a[y - 1]) % p : a[x + y - 1];
}
void dfs(int x) {
    if (dep[x])
        ans = (ans + calc(cnt, D)) % p;
    for (auto to : e[x])
        if (to != fa[x]) {
            cnt += vis[to];
            D += 1 - vis[to];
            dfs(to);
            cnt -= vis[to];
            D -= 1 - vis[to];
        }
}
signed main() {
    n = read(), k = read(), s = read();
    for (int i = 1; i <= n - 1; i++) {
        int x = read(), y = read();
        e[x].pb(y);
        e[y].pb(x);
    }
    dep[0] = -1;dfs(1, 0);
    for (int x = s; x; x = fa[x])
        vis[x] = 1;
    a[0] = 1;
    for (int i = 1; i <= n; i++)
        a[i] = a[i - 1] * (n - i - k) % p * inv(n - i, p - 2) % p;
    for (int i = 1; i <= n; i++)
        a[i] = (a[i] + a[i - 1]) % p;
    dfs(1);
    ans = (ans * 2 - dep[s] + p) % p;
    printf("%lld\n", ans);
    return 0;
}

E

如果两个电线相交,我们交换第一个相遇的位置以后的路径,这样就从原本的 变成了 ,也就是交换终点。因为不合法的方案与 两两对应,所以最后答案就是 的方案数减去 的方案数。

考虑如何计算从 的方案数(其余同理)。对于一条弦 ,设交点编号按照从 的顺序分别为 。 发现贡献这些交点的弦的起点也是从小到大的顺序。并且对于一条从 进入的路径,可以从任意 出去。那么记录 表示终点在第 条最后一个交点的方案数。 表示终点在圆上第 个顶点的方案数。对于转移,有如下几种:

  • 当前点由上一个点走一条圆弧得来,
  • 当前点是一条弦 的终点,
  • 以当前起点新增一条弦 ,按交点顺序遍历弦 。显然 就是所有 的和加上 是这条弦的起点)。由于弦 和弦 的交点肯定是弦 的最后一个交点,所以对所有的 做一遍前缀和再加上

时间复杂度

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 9;
const int p = 998244353;
int n, m, L, res1, res2;
int a[N], id[N], f[N], g[N];
int read() {
    int f = 0, x = 0;
    char ch = getchar();
    while (!isdigit(ch)) {
        f |= (ch == '-');
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return f ? -x : x;
}
void work(int S) {
    memset(g, 0, sizeof g);
    memset(f, 0, sizeof f);
    f[S] = 1;
    for (int i = S, j = 1; i <= n; i++) {
        if(i != S)f[i] = f[i - 1];
        if (i == a[j] + L)
            (f[i] += g[j]) %= p, j++;
        if (!id[i])
            continue;
        int sum = f[i];
        for (int k = j; k <= id[i]; k++)
            (g[k] += sum) %= p, sum = g[k];
    }
}
signed main() {
    n = read(), m = read(), L = read();
    for (int i = 1; i <= m; i++)
        a[i] = read();
    sort(a + 1, a + 1 + m);
    for (int i = 1; i <= m; i++)
        id[a[i]] = i;
    work(1);
    res1 = f[n] + 1, res2 = f[n - 1];
    work(2);
    res1 = (1ll * res1 * f[n - 1] + p - 1ll * res2 * f[n] % p ) % p;
    cout << res1 << endl;
    return 0;
}

F

对于树上的问题,发现有解当且仅当图是一条链,且询问的两点是链的端点。

对于一般无向图,有解当且仅当建出圆方树后当且仅当方点构成一条链,询问两点分别位于两个方点端点,并且两个点不能是割点。

求解点双,时间复杂度

#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int N = 4e5 + 9;
int n, m, q, idx, top;
int dfn[N], sta[N], low[N], color, d[N], w[N];
vector <int> c[N], e[N], E[N];
void chmn(int &x, int y) {
    (x > y) &&(x = y);
}
int read() {
    int f = 0, x = 0;
    char ch = getchar();
    while (!isdigit(ch)) {
        f |= (ch == '-');
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return f ? -x : x;
}
void tarjan(int x) {
    dfn[x] = low[x] = ++ idx;
    sta[++ top] = x;
    for (auto to : e[x]) {
        if (!dfn[to]) {
            tarjan(to), chmn(low[x], low[to]);
            if (low[to] >= dfn[x]) {
                c[++ color].push_back(x);
                int now = 0;
                while (now != to)
                    now = sta[top --], c[color].pb(now);
            }
        } else
            low[x] = min(low[x], dfn[to]);
    }
}
bool flag = 1;
int main() {
    n = read(), m = read();
    for (int i = 1; i <= m; i++) {
        int x = read(), y = read();
        e[x].pb(y), e[y].pb(x);
    }
    tarjan(1);
    for (int i = 1; i <= n; i++)
        if (!dfn[i]) {
            flag = 0;
            break;
        }
    int L = 0, R = 0;
    if (flag) {
        for (int i = 1; i <= color; i++)
            for (auto x : c[i])
                E[n + i].pb(x), E[x].pb(n + i), ++ d[x], ++ d[n + i];
        for (int x = 1; x <= n + color; x++)
            if (d[x] > 1)
                for (auto to : E[x])
                    if (d[to] > 1)
                        ++ w[x];
        for (int i = 1; i <= n + color; i++)
            if (d[i] > 1) {
                if (w[i] > 2) {
                    flag = 0;
                    break;
                }
                if (!w[i]) {
                    L = R = i;
                    break;
                }
                if (w[i] == 1) {
                    L = R;
                    R = i;
                }
            }
    }
    q = read();
    while (q --) {
        int x = read(), y = read();
        if (!flag) {
            puts("NO");
            continue;
        }
        if (E[x].size() != 1 || E[y].size() != 1) {
            puts("NO");
            continue;
        }
        int fx = E[x][0], fy = E[y][0];
        if (fx > fy)
            swap(fx, fy);
        puts((fx == L && fy == R) ? "YES" : "NO");
    }
    return 0;
}

G

若把一个凸包看成相对于另一个凸包在移动,就转化为只有一个凸包在移动的问题了!

首先他们相撞时,一定是其中一个凸包的顶点与另一个凸包有交,这启发我们枚举相交顶点。

问题变成了一个凸包移动多久后会与一个点 相交。求法就是做一条平行于凸包速度,过 的直线 ,标记 和凸包的交点为 ,答案就是 之间的距离除以速度。

但因为这条直线和凸包可能会有两个交点,所以需要按照速度的法向量将凸包切成两半。而对于某一半凸包和一个点 的答案,只需要二分求出交点在凸包哪条线段上即可。

还有一种方法就是二分时间求出移动距离。做闵可夫斯基和求出凸包扫过的区域,然后判断两个凸包是否有交。但是常数略大。

#include <bits/stdc++.h>
#define y1 regrtefbgth
using namespace std;
using point = complex<double>;
#define x real
#define y imag
struct line {
    point a;
    point b;
    line(const point &a, const point &b) : a(a), b(b) {}
};
int sgn(const point &a) {
    if (a.y() > 0 || (a.y() == 0 && a.x() > 0)) {
        return 1;
    } else {
        return 0;
    }
}
double dot(const point &a, const point &b) {
    return (conj(a) * b).x();
}
double cross(const point &a, const point &b) {
    return (conj(a) * b).y();
}
point intersection(const line &l1, const line &l2) {
    return l1.a + (l1.b - l1.a) * (cross(l2.b - l2.a, l1.a - l2.a) / cross(l2.b - l2.a, l1.a - l1.b));
}
int n, m;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout << fixed << setprecision(10);
    cin >> n;
    vector<point> a(n);
    for (int i = 0; i < n; i++) {
        int x, y;
        cin >> x >> y;
        a[i] = point(x, y);
    }
    cin >> m;
    vector<point> b(m);
    for (int i = 0; i < m; i++) {
        int x, y;
        cin >> x >> y;
        b[i] = point(-x, -y);
    }
    int x1, y1, x2, y2;
    cin >> x1 >> y1 >> x2 >> y2;
    point v(x2 - x1, y2 - y1);
    point sa = a[0], sb = b[0];
    for (int i = 0; i < n; i++) {
        if (a[i].y() < sa.y() || (a[i].y() == sa.y() && a[i].x() < sa.x())) {
            sa = a[i];
        }
    }
    for (int i = 0; i < m; i++) {
        if (b[i].y() < sb.y() || (b[i].y() == sb.y() && b[i].x() < sb.x())) {
            sb = b[i];
        }
    }
    auto s = sa + sb;
    vector<point> d(n + m);
    for (int i = 0; i < n; i++) {
        d[i] = a[(i + 1) % n] - a[i];
    }
    for (int i = 0; i < m; i++) {
        d[n + i] = b[(i + 1) % m] - b[i];
    }
    sort(d.begin(), d.end(), [&](point a, point b) {
        if (sgn(a) != sgn(b)) {
            return sgn(a) == 1;
        }
        return cross(a, b) > 0;
    });
    vector<point> c(n + m);
    c[0] = s;
    for (int i = 0; i < n + m - 1; i++) {
        c[i + 1] = c[i] + d[i];
    }
    int N = n + m;
    bool in = true;
    for (int i = 0; i < N; i++) {
        if (cross(c[i], c[(i + 1) % N]) < 0) {
            in = false;
        }
    }
    if (in) {
        puts("0");
        return 0;
    }
    if (v == point(0, 0)) {
        puts("-1");
        return 0;
    }
    double ans = -1;
    for (int i = 0; i < N; i++) {
        auto a = c[i], b = c[(i + 1) % N];
        if (cross(b - a, v) == 0) {
            continue;
        }
        auto p = intersection(line(0, v), line(a, b));
        if (dot(v, p) >= 0 && dot(p - a, b - a) >= 0 && dot(p - b, a - b) >= 0) {
            double t = sqrt(dot(p, p) / dot(v, v));

            if (ans < 0 || ans > t) {
                ans = t;
            }
        }
    }
    if (ans < 0) {
        puts("-1");
        return 0;
    }
    cout << ans << endl;
    return 0;
}

H

前置知识:后缀数组

先将所有串连到一起,中间加分隔符,然后后缀排序,并求出 数组。

对于 中以第 个字符为开头的后缀,设它的 ,求出 串中 小的最大的 值和 大的最小的 值,分别设为 ,那么该后缀所能贡献的最大的答案(不妨设为 )即为 。其中 表示 的最大子段和, 求法可以使用线段树维护,具体方法可见 GSS1 的题解。

剩余的问题是如何找到 的前驱后继(即 ),只需要枚举 ,用变量记录最后一次出现的 中位置,正反各扫一遍即可,求 区间 也不需要 表,在遇到 中位置时改为该位置 ,否则取 即可。

时间复杂度

#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxm = 1e5 + 10, maxn = 1.2e6 + 10;
std::vector<int> sa_is(const std::vector<int> &s, int upper) {
    int n = int(s.size());
    std::vector<int> sa(n);
    std::vector<bool> ls(n);

    for (int i = n - 2; i >= 0; i--) {
        ls[i] = (s[i] == s[i + 1]) ? ls[i + 1] : (s[i] < s[i + 1]);
    }

    std::vector<int> sum_l(upper + 1), sum_s(upper + 1);

    for (int i = 0; i < n; i++) {
        if (!ls[i]) {
            sum_s[s[i]]++;
        } else {
            sum_l[s[i] + 1]++;
        }
    }

    for (int i = 0; i <= upper; i++) {
        sum_s[i] += sum_l[i];

        if (i < upper)
            sum_l[i + 1] += sum_s[i];
    }

    auto induce = [&](const std::vector<int> &lms) {
        std::fill(sa.begin(), sa.end(), -1);
        std::vector<int> buf(upper + 1);
        std::copy(sum_s.begin(), sum_s.end(), buf.begin());

        for (auto d : lms) {
            if (d == n)
                continue;

            sa[buf[s[d]]++] = d;
        }

        std::copy(sum_l.begin(), sum_l.end(), buf.begin());
        sa[buf[s[n - 1]]++] = n - 1;

        for (int i = 0; i < n; i++) {
            int v = sa[i];

            if (v >= 1 && !ls[v - 1]) {
                sa[buf[s[v - 1]]++] = v - 1;
            }
        }

        std::copy(sum_l.begin(), sum_l.end(), buf.begin());

        for (int i = n - 1; i >= 0; i--) {
            int v = sa[i];

            if (v >= 1 && ls[v - 1]) {
                sa[--buf[s[v - 1] + 1]] = v - 1;
            }
        }
    };

    std::vector<int> lms_map(n + 1, -1);
    int m = 0;

    for (int i = 1; i < n; i++) {
        if (!ls[i - 1] && ls[i]) {
            lms_map[i] = m++;
        }
    }

    std::vector<int> lms;
    lms.reserve(m);

    for (int i = 1; i < n; i++) {
        if (!ls[i - 1] && ls[i]) {
            lms.push_back(i);
        }
    }

    induce(lms);

    if (m) {
        std::vector<int> sorted_lms;
        sorted_lms.reserve(m);

        for (int v : sa) {
            if (lms_map[v] != -1)
                sorted_lms.push_back(v);
        }

        std::vector<int> rec_s(m);
        int rec_upper = 0;
        rec_s[lms_map[sorted_lms[0]]] = 0;

        for (int i = 1; i < m; i++) {
            int l = sorted_lms[i - 1], r = sorted_lms[i];
            int end_l = (lms_map[l] + 1 < m) ? lms[lms_map[l] + 1] : n;
            int end_r = (lms_map[r] + 1 < m) ? lms[lms_map[r] + 1] : n;
            bool same = true;

            if (end_l - l != end_r - r) {
                same = false;
            } else {
                while (l < end_l) {
                    if (s[l] != s[r]) {
                        break;
                    }

                    l++;
                    r++;
                }

                if (l == n || s[l] != s[r])
                    same = false;
            }

            if (!same)
                rec_upper++;

            rec_s[lms_map[sorted_lms[i]]] = rec_upper;
        }

        auto rec_sa =
            sa_is(rec_s, rec_upper);

        for (int i = 0; i < m; i++) {
            sorted_lms[i] = lms[rec_sa[i]];
        }

        induce(sorted_lms);
    }

    return sa;
}
int id[maxn], inf = 255, len, mx, n, pos[maxm], rk[maxn];
vector<int>s, sa;
char t[maxm];
inline void suffix_sort() {
    sa = sa_is(s, inf);

    for (int &i : sa)
        ++i;

    sa.insert(sa.begin(), 0);

    for (int i = 0; i < sa.size(); ++i)
        rk[sa[i]] = i;
}
int ht[maxn];
inline void calc_ht() {
    for (int i = 1, h = 0; i <= n; ++i) {
        if (h)
            --h;

        int j = sa[rk[i] - 1];

        while (s[i - 1 + h] == s[j - 1 + h])
            ++h;

        ht[rk[i]] = h;
    }
}
int a[maxm], k, m;
struct node {
    ll l, mx, r, sum;
    inline node(ll v = 0): mx(v) {}
    inline node(ll l_, ll mx_, ll r_, ll sum_): l(l_), mx(mx_), r(r_), sum(sum_) {}
    inline node operator+(const node &k)const {
        if (mx == LLONG_MIN)
            return k;

        return {max(l, sum + k.l), max({mx, r + k.l, k.mx}), max(r + k.sum, k.r), sum + k.sum};
    }
};
struct SGT {
    node v[maxm << 2];
    void build(int p, int l, int r) {
        if (l == r) {
            v[p] = {a[l], a[l], a[l], a[l]};
            return;
        }

        int mid = l + r >> 1;
        build(p << 1, l, mid);
        build(p << 1 | 1, mid + 1, r);
        v[p] = v[p << 1] + v[p << 1 | 1];
    }
    node query(int p, int l, int r, int ql, int qr) {
        if (ql <= l && r <= qr)
            return v[p];

        int mid = l + r >> 1;
        node ret(LLONG_MIN);

        if (ql <= mid)
            ret = ret + query(p << 1, l, mid, ql, qr);

        if (qr > mid)
            ret = ret + query(p << 1 | 1, mid + 1, r, ql, qr);

        return ret;
    }
} T;
ll ans[maxm];
int main() {
    scanf("%d%d%d%s", &n, &m, &k, t + 1);
    len = n;

    for (int i = 1; i <= len; ++i)
        s.push_back(t[i]);

    for (int i = 1; i <= m; ++i)
        scanf("%d", a + i);

    T.build(1, 1, m);

    for (int i = 1; i <= k; ++i) {
        scanf("%s", t + 1);
        pos[i] = ++n, s.push_back(++inf);

        for (int j = 1; j <= m; ++j)
            id[++n] = i, s.push_back(t[j]);
    }

    suffix_sort();
    calc_ht();

    for (int i = 1, h = 0; i <= n; ++i) {
        if (1 <= sa[i] && sa[i] <= len)
            h = ht[i + 1];
        else if (h && id[sa[i]])
            ans[id[sa[i]]] = max(ans[id[sa[i]]], T.query(1, 1, m, sa[i] - pos[id[sa[i]]],
                                 sa[i] - pos[id[sa[i]]] + h - 1).mx);

        h = min(h, ht[i + 1]);
    }

    for (int i = n, h = 0; i; --i) {
        if (1 <= sa[i] && sa[i] <= len)
            h = ht[i];
        else if (h && id[sa[i]])
            ans[id[sa[i]]] = max(ans[id[sa[i]]], T.query(1, 1, m, sa[i] - pos[id[sa[i]]],
                                 sa[i] - pos[id[sa[i]]] + h - 1).mx);

        h = min(h, ht[i]);
    }

    for (int i = 1; i <= k; ++i)
        printf("%lld\n", ans[i]);

    return 0;
}

J

由于到达一个点时具有方向,所以我们把边看成点,在十字路口的时候连边。由于边权只有 两种,所以用 可以做到 。具体实现为,若到下一个点的边权为 ,就塞入队头,否则塞入队尾。每次取队头更新。也可以用优先队列直接跑最短路。

#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 9;
struct node {
    int x, y, w;
};
int n, sx, sy, tx, ty, ans;
int v[N][4], dis[N][4], p[5];
int read() {
    int f = 0, x = 0;
    char ch = getchar();
    while (!isdigit(ch)) {
        f |= (ch == '-');
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return f ? -x : x;
}
int main() {
    deque<node>q;
    n = read();
    for (int i = 1; i <= n; i++)
        for (int j = 0; j < 4; j++) {
            v[i][j] = read();
            dis[i][j] = N;
        }
    sx = read();
    sy = read();
    tx = read();
    ty = read();
    q.push_front(node{sx, sy, 0});
    while (q.size()) {
        int x, y, w, nt;
        node now = q.front();
        q.pop_front();
        x = now.x;
        y = now.y;
        w = now.w;
        if (!y)
            continue;
        for (int i = 0; i < 4; i++)
            if (v[x][i] == y)
                nt = i;
        if (dis[x][nt] <= w)
            continue;
        else
            dis[x][nt] = w;
        for (int i = 0; i < 4; i++)
            if (v[y][i] == x)
                nt = (i + 1) % 4;
        for(int i = 0; i < 4; i++){
            if(i == nt) q.push_front(node{y, v[y][i], w});
            else q.push_back(node{ y, v[y][i], w + 1});
        }
    }
    for (int i = 0; i < 4; i++)
        if (v[tx][i] == ty)
            ans = dis[tx][i];
    if (ans == N)
        ans = -1;
    cout << ans << endl;
    return 0;
}

全部评论

(0) 回帖
加载中...
话题 回帖

等你来战

查看全部

热门推荐