「Fortuna OJ」Jan 12 省选组 – 解题报告

无论你再怎么觉得稳,比赛完了之后仍会挂分,而且你会发现稍稍改下就他妈可以加几十分。这就是生活。

kal0rona

A – 历史行程

官方正解写的是 SA,我不会,所以我就搜了半天 SAM 的题解,发现了一种很好的做法。

我们考虑建个 SAM,并且用 set 维护 Right 集合。然后在 Link 树上 DFS。因为我们的每次询问下表是连续的,所以对于树上点 \(u\) 的 set 中的某一个下标而言,他所能做的贡献就是将和子树内其前驱后继进行更新,也就是两个三元组\((u_x, sub_1, dep_u)\)、\((sub_2, u_x, dep_u)\)。因为,如果\(u\)在\(sub\)中选\(y\)时,如果选到了更远的\(z\),那么显然答案不如\((y, z)\)的优,因为其深度更大时就已经完成了更新。set 可以用启发式合并,具体方式类似于「春节十二响」,通过交换编号实现。

然后再排序 + 离线 + 扫描线找最大点即可,还是很有意思的。

// A.cpp
#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 2e6 + 200, bitnum = 17;
typedef unsigned long long ull;

int n, q, head[MAX_N], current, sid[MAX_N], tp_tot, ans[MAX_N];
char str[MAX_N];
set<int> st[MAX_N];

struct edge
{
    int to, nxt;
} edges[MAX_N << 1];

struct triple
{
    int x, y, d;
    bool operator<(const triple &rhs) const { return x < rhs.x; }
} tps[MAX_N << 1], queries[MAX_N];

void addpath(int src, int dst)
{
    edges[current].to = dst, edges[current].nxt = head[src];
    head[src] = current++;
}

void fileIO()
{
    freopen("history.in", "r", stdin);
    freopen("history.out", "w", stdout);
}

struct node
{
    int ch[2], link, dep;
} nodes[MAX_N];

int roots[MAX_N], atPos[MAX_N], ptot = 1, last_ptr = 1;
int fa[20][MAX_N], tree[MAX_N];

inline int lowbit(int x) { return x & (-x); }

void update(int x, int d)
{
    for (; x <= n; x += lowbit(x))
        tree[x] = max(tree[x], d);
}

int query(int x, int ret = 0)
{
    for (; x; x -= lowbit(x))
        ret = max(ret, tree[x]);
    return ret;
}

void insert(int c, int pos)
{
    int pre = last_ptr, p = last_ptr = ++ptot;
    nodes[p].dep = nodes[pre].dep + 1, st[p].insert(pos);
    while (pre && nodes[pre].ch[c] == 0)
        nodes[pre].ch[c] = p, pre = nodes[pre].link;
    if (pre == 0)
        nodes[p].link = 1;
    else
    {
        int q = nodes[pre].ch[c];
        if (nodes[q].dep == nodes[pre].dep + 1)
            nodes[p].link = q;
        else
        {
            int clone = ++ptot;
            nodes[clone] = nodes[q], nodes[clone].dep = nodes[pre].dep + 1;
            nodes[q].link = nodes[p].link = clone;
            while (pre && nodes[pre].ch[c] == q)
                nodes[pre].ch[c] = clone, pre = nodes[pre].link;
        }
    }
}

void dfs(int u)
{
    for (int i = head[u]; i != -1; i = edges[i].nxt)
    {
        dfs(edges[i].to);
        if (st[sid[edges[i].to]].size() > st[sid[u]].size())
            swap(sid[edges[i].to], sid[u]);
        for (set<int>::iterator it = st[sid[edges[i].to]].begin(); it != st[sid[edges[i].to]].end(); it++)
        {
            set<int>::iterator tmp = st[sid[u]].upper_bound(*it);
            if (tmp != st[sid[u]].end())
                tps[++tp_tot] = triple{*it, *tmp, nodes[u].dep};
            if (tmp != st[sid[u]].begin())
                tps[++tp_tot] = triple{*(--tmp), *it, nodes[u].dep};
            st[sid[u]].insert(*it);
        }
    }
}

int main()
{
    memset(head, -1, sizeof(head));
    // fileIO();
    scanf("%d%d%s", &n, &q, str + 1);
    for (int i = 1; i <= n; i++)
        insert(str[i] - '0', i);
    sid[1] = 1;
    for (int i = 2; i <= ptot; i++)
        addpath(nodes[i].link, i), sid[i] = i;
    dfs(1);
    for (int i = 1; i <= q; i++)
        scanf("%d%d", &queries[i].x, &queries[i].y), queries[i].d = i;
    sort(tps + 1, tps + 1 + tp_tot), sort(queries + 1, queries + 1 + q);
    int tp_ptr = tp_tot;
    for (int q_ptr = q; q_ptr >= 1; q_ptr--)
    {
        while (tp_ptr >= 1 && tps[tp_ptr].x >= queries[q_ptr].x)
            update(tps[tp_ptr].y, tps[tp_ptr].d), tp_ptr--;
        ans[queries[q_ptr].d] = query(queries[q_ptr].y);
    }
    for (int i = 1; i <= q; i++)
        printf("%d\n", ans[i]);
    return 0;
}

B – 跳蚤王国

先找到中心作为根,然后把根的儿子放到 vector 中称为「候补割点」。我们按照子树大小从大到小对「候补割点」进行排序,把前 \(x\) 个称为「割点」,其中这些点的子树大小和恰好大于等于 \(\frac{n}{2}\)。

现在我们考虑做若干次 DFS 来获取答案。我们先枚举割点做子树根做 DFS,每个点的答案就为\(x\)或\(x-1\)。如何判断是否减一?我们只需要判断割点根的向上树边是否有必要即可。

// B.cpp
#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 1e6 + 200;

int head[MAX_N], current, n, siz[MAX_N], root, answer[MAX_N], acc, limit_id;
vector<int> vec;

struct edge
{
    int to, nxt;
} edges[MAX_N << 1];

void fileIO()
{
    freopen("flea.in", "r", stdin);
    freopen("flea.out", "w", stdout);
}

bool compare(int a, int b) { return siz[a] > siz[b]; }

void addpath(int src, int dst)
{
    edges[current].to = dst, edges[current].nxt = head[src];
    head[src] = current++;
}

void dfs(int u, int fa)
{
    int max_son = 0;
    siz[u] = 1;
    for (int i = head[u]; i != -1; i = edges[i].nxt)
        if (edges[i].to != fa)
            dfs(edges[i].to, u), siz[u] += siz[edges[i].to], max_son = max(max_son, siz[edges[i].to]);
    max_son = max(max_son, n - siz[u]);
    if (max_son <= (n >> 1))
        root = u;
}

void getAns(int u, int fa)
{
    answer[u] = limit_id - (siz[u] + acc >= (n >> 1)) + 1;
    for (int i = head[u]; i != -1; i = edges[i].nxt)
        if (edges[i].to != fa)
            getAns(edges[i].to, u);
}

int main()
{
    // fileIO();
    memset(head, -1, sizeof(head));
    scanf("%d", &n);
    for (int i = 1, u, v; i <= n - 1; i++)
        scanf("%d%d", &u, &v), addpath(u, v), addpath(v, u);
    dfs(1, 0), dfs(root, 0);
    for (int i = head[root]; i != -1; i = edges[i].nxt)
        vec.push_back(edges[i].to);
    sort(vec.begin(), vec.end(), compare);
    limit_id = -1;
    for (int i = 0, siz_ = vec.size(); i < siz_; i++)
    {
        acc += siz[vec[i]];
        if (acc >= (n >> 1))
        {
            limit_id = i;
            break;
        }
    }
    for (int i = 0; i <= limit_id; i++)
        acc -= siz[vec[i]], getAns(vec[i], root), acc += siz[vec[i]];
    if (limit_id != -1)
        acc -= siz[vec[limit_id]];
    for (int i = limit_id + 1, siz_ = vec.size(); i < siz_; i++)
        getAns(vec[i], root);
    for (int i = 1; i <= n; i++)
        printf("%d\n", answer[i]);
    return 0;
}

C – 围墙

首先,我们把\(i \to p_i\)进行连边,会得到一个有很多环的图。分析题意,我们发现这道题要求我们把一些边删去,留下\(\frac{n}{2}\)个单边,且每条边不相交。那么,对于长度为\(2\)的环,我们把编号小的点作为左括号,编号大的点作为右括号;对于长度为偶数的环,我们发现也只有两种选择,直接搜索即可。

// C.cpp
#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 110;
typedef long long ll;
typedef pair<int, ll> pil;

int n, pi[MAX_N], deg[MAX_N], seq[MAX_N];

void fileIO()
{
    freopen("wall.in", "r", stdin);
    freopen("wall.out", "w", stdout);
}

bool dfs(int pos, int acc)
{
    if (acc < 0)
        return false;
    if (pos == n + 1)
        return true;
    if (seq[pos] != 0)
        return dfs(pos + 1, acc + seq[pos]);
    int x = pos, len = 0, sign = 1;
    while (len == 0 || x != pos)
        seq[x] = sign, x = pi[x], len++, sign = -sign;
    if (dfs(pos + 1, acc + seq[pos]))
        return true;
    x = pos, len = 0, sign = -1;
    while (len == 0 || x != pos)
        seq[x] = sign, x = pi[x], len++, sign = -sign;
    if (dfs(pos + 1, acc + seq[pos]))
        return true;
    seq[pos] = 0;
    return false;
}

int main()
{
    // fileIO();
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &pi[i]);
    for (int i = 1; i <= n; i++)
        if (pi[i] > i && i == pi[pi[i]])
            seq[i] = 1, seq[pi[i]] = -1;
    if (dfs(1, 0))
        for (int i = 1; i <= n; i++)
            if (seq[i] == 1)
                putchar('(');
            else
                putchar(')');
    return 0;
}

 

Leave a Reply

Your email address will not be published. Required fields are marked *