【多校训练】2022杭电多校5补题

官方数据和标程:2022杭电多校资料包

难度评价

5题一百名,4题两百名,3题慢四百名。至少补到5、6题,金牌补到8题以上。
C、J、L签到,D、F、G中等,A、B、H困难,I、K大难,E没用。


A. Pandaemonium Asphodelos: The First Circle (Savage)

数据结构

要求支持4种操作:

  1. 将离 x x x 最近的 2 c 2c 2c 个砖块赋予一个新属性,属性序号可直接记作当前操作ID。
  2. x x x 的属性赋到与 y y y 相连且与其属性相同的最长连续段上。
  3. x x x 对应的属性的权值增加 v v v
  4. 查询 x x x 的权值。

考虑分别维护砖块的属性和权值。
属性需要区间推平操作,容易想到珂朵莉树(ODT),因为本题复杂度来源全是assign,效率有保证;权值需要单点查询和区间加,考虑线段树,由于 n n n 规模较大,必须动态开点。

具体地,
对于操作1,找到对应段,直接assign即可。这里“最近”的定义就是一维意义上的,且输入保证有 2 c + 1 ≤ n 2c+1 \leq n 2c+1n,即段长一定是 2 c + 1 2c+1 2c+1,只是中心点不一定是 x x x
对于操作2,先查找 x x x 的属性,再找到 y y y 所在的段,assign即可。这要求我们在维护过程中总是保证每段是最大连续段,因此assign实现中需要检查合并相邻的相同属性段。
对于操作3,找到 x x x 的属性,因为可能有很多零散的段,因此用一个数组 a d d [ N ] add[N] add[N] 表示每种属性权值增量,直接加到数组上即可。因为使用了这种增量的形式维护权值,当一段的属性改变时,该段所有砖块权值要加上 a d d [ a t t r l a s t ] − a d d [ a t t r n o w ] add[attr_{last}]-add[attr_{now}] add[attrlast]add[attrnow],这是一个线段树区间加。
对于操作4,直接线段树单点查询。

#include <bits/stdc++.h>
using namespace std;

#define ls tr[rt].lc
#define rs tr[rt].rc
constexpr int MAXN = 1e5 + 5;
struct SegNode
{
    int lc, rc;
    int64_t val, tag;
} tr[MAXN << 5];
int n, q, tot;
int64_t add[MAXN];

inline int newNode()
{
    ++tot;
    tr[tot].lc = tr[tot].rc = tr[tot].val = tr[tot].tag = 0;
    return tot;
}

void pushDown(int rt)
{
    if (tr[rt].tag)
    {
        if (!ls)
            ls = newNode();
        if (!rs)
            rs = newNode();
        tr[ls].val += tr[rt].tag, tr[ls].tag += tr[rt].tag;
        tr[rs].val += tr[rt].tag, tr[rs].tag += tr[rt].tag;
        tr[rt].tag = 0;
    }
}

void modify(int rt, int L, int R, int64_t C, int l, int r)
{
    if (L <= l && r <= R)
    {
        tr[rt].val += C, tr[rt].tag += C;
        return;
    }
    int mid = (l + r) >> 1;
    pushDown(rt);

    if (L <= mid)
    {
        if (!ls)
            ls = newNode();
        modify(ls, L, R, C, l, mid);
    }
    if (R > mid)
    {
        if (!rs)
            rs = newNode();
        modify(rs, L, R, C, mid + 1, r);
    }
}

int64_t query(int rt, int L, int l, int r)
{
    if (rt == 0)
        return 0;
    if (l == r)
        return tr[rt].val;
    int mid = (l + r) >> 1;
    pushDown(rt);

    if (L <= mid)
        return query(ls, L, l, mid);
    else
        return query(rs, L, mid + 1, r);
}

struct Node
{
    int l, r;
    mutable int v;
    Node(int l, int r = 0, int v = 0) : l(l), r(r), v(v) {}
    bool operator<(const Node &rhs) const { return l < rhs.l; }
};
set<Node> s;

set<Node>::iterator split(int pos)
{
    auto it = s.lower_bound({pos});
    if (it != s.end() && it->l == pos)
        return it;
    --it;
    if (it->r < pos)
        return s.end();
    int l = it->l, r = it->r, v = it->v;
    s.erase(it);
    s.emplace(l, pos - 1, v);
    return s.emplace(pos, r, v).first;
}

void assign(int l, int r, int v)
{
    auto R = split(r + 1), L = split(l);
    while (L != s.begin() && prev(L)->v == v)
        --L, l = L->l;
    while (R != s.end() && R->v == v)
        r = R->r, ++R;
    for (auto it = L; it != R; it++)
        if (add[it->v] != add[v])
            modify(1, it->l, it->r, add[it->v] - add[v], 1, n);
    s.erase(L, R);
    s.emplace(l, r, v);
}

set<Node>::iterator find(int pos) { return --s.upper_bound({pos}); }

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while (t--)
    {
        int lst = 0;
        cin >> n >> q;
        s.clear(), s.emplace(1, n, 0);
        tot = 1, tr[1].lc = tr[1].rc = tr[1].tag = tr[1].val = 0;
        fill(add, add + q + 1, 0);
        for (int i = 1; i <= q; i++)
        {
            int op, x, y;
            cin >> op >> x, x = ((x - 1) ^ lst) % n + 1;
            if (op == 1)
            {
                cin >> y, y = ((y - 1) ^ lst) % ((n - 1) / 2) + 1;
                int l = x - y, r = x + y;
                if (l < 1)
                    r += 1 - l, l += 1 - l;
                else if (r > n)
                    l -= r - n, r -= r - n;
                assign(l, r, i);
            }
            else if (op == 2)
            {
                cin >> y, y = ((y - 1) ^ lst) % n + 1;
                auto it1 = find(x), it2 = find(y);
                if (it1->v != it2->v)
                    assign(it2->l, it2->r, it1->v);
            }
            else if (op == 3)
            {
                cin >> y, add[find(x)->v] += y;
            }
            else
            {
                auto ans = query(1, x, 1, n) + add[find(x)->v];
                cout << ans << "\n";
                lst = ans & 1073741823;
            }
        }
    }

    return 0;
}

B. Jo loves counting

数学暴力

过难


C. Slipper

图论

明显要跑最短路,但是建边上不能过于暴力,考虑每层新增一个中转点,它到本层点的边权为 0 0 0,与其相差 k k k 层的结点都可以跳转到它且边权为 p p p

注:机子太慢了,不用前向星容易被卡常。

#include <bits/stdc++.h>
using namespace std;
#define int int64_t

constexpr int MAXN = 2e6 + 5, MAXM = 2e7;
using PII = pair<int, int>;
int head[MAXN], tot = 1;
int d[MAXN], dep[MAXN], mxdep;
bool vis[MAXN];
struct Edge
{
    int to, next, w;
} e[MAXM];
void add(int u, int v, int w)
{
    e[++tot].to = v, e[tot].w = w, e[tot].next = head[u];
    head[u] = tot;
}
void clear(int n)
{
    for (int i = 1; i <= n + mxdep; i++)
        head[i] = 0;
    tot = 1;
}

void dfs(int v, int fa)
{
    dep[v] = dep[fa] + 1;
    mxdep = max(mxdep, dep[v]);
    for (int i = head[v]; i; i = e[i].next)
        if (e[i].to != fa)
            dfs(e[i].to, v);
}

void dijkstra(int s)
{
    memset(vis, 0, sizeof(vis));
    memset(d, 0x3f, sizeof(d));
    d[s] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> q;
    q.emplace(0, s);
    while (!q.empty())
    {
        auto [_, v] = q.top();
        q.pop();
        if (vis[v])
            continue;
        vis[v] = 1;

        for (int i = head[v]; i; i = e[i].next)
        {
            int u = e[i].to, w = e[i].w;
            if (!vis[u] && d[v] + w < d[u])
                d[u] = d[v] + w, q.emplace(d[u], u);
        }
    }
}

int32_t main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int T;
    cin >> T;
    while (T--)
    {
        int n, k, p, s, t;
        cin >> n;
        for (int i = 1; i < n; i++)
        {
            int v, u, w;
            cin >> v >> u >> w, add(v, u, w), add(u, v, w);
        }
        cin >> k >> p >> s >> t;
        mxdep = 0;
        dfs(1, 0);
        for (int i = 1; i <= n; i++)
        {
            add(n + dep[i], i, 0);
            if (dep[i] - k >= 1)
                add(i, n + dep[i] - k, p);
            if (dep[i] + k <= mxdep)
                add(i, n + dep[i] + k, p);
        }
        dijkstra(s);
        cout << d[t] << "\n";
        clear(n);
    }

    return 0;
}

D. The Surveying

计算几何

数据范围已经给出了重要提示,那么实际上这题就做完了。容易想到 O ( 2 N ) O(2^N) O(2N) 状压枚举控制点,检查是否能看到所有Detail Points且控制点间连通,考虑用bitset优化一下,这部分复杂度为 O ( 2 N ⋅ N ( N + M ) w ) O(2^{N} \cdot \cfrac{N(N+M)}{w}) O(2NwN(N+M))。预处理时对每个控制点,暴力求出其能看到的Detail Points和控制点,复杂度 O ( N M 2 + N 2 M ) O(NM^2+N^2M) O(NM2+N2M),做法为判断线段相交。控制点之间的线段不应该与任何矩形边相交;控制点和Detail Point之间的线段如果与矩形边相交,交点必须是该Detail Point。控制点在矩形边界延长线上的情况可能会带来一些疑惑,实际上不需要特判。

#include <bits/stdc++.h>
using namespace std;

struct Point
{
    int64_t x, y;
    Point() {}
    Point(int64_t x, int64_t y) : x(x), y(y) {}
    bool operator!=(Point B) { return x != B.x || y != B.y; }
    Point operator-(Point B) { return Point(x - B.x, y - B.y); }
} ctrl[20], detail[400];
using Vector = Point;

int64_t Cross(Vector A, Vector B) { return A.x * B.y - A.y * B.x; }

bool Cross_segment(Point a, Point b, Point c, Point d)
{
    __int128_t c1 = Cross(b - a, c - a), c2 = Cross(b - a, d - a);
    __int128_t d1 = Cross(d - c, a - c), d2 = Cross(d - c, b - c);
    return c1 * c2 < 0 && d1 * d2 < 0; // 这里爆ll了,看了很久
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while (t--)
    {
        int n, m;
        cin >> n >> m;
        vector<bitset<400>> bs1(n);
        vector<bitset<20>> bs2(n);
        for (int i = 0; i < n; i++)
            cin >> ctrl[i].x >> ctrl[i].y;
        for (int i = 0; i < m * 4; i++)
            cin >> detail[i].x >> detail[i].y;

        for (int i = 0; i < n; i++)
        {
            for (int j = i + 1; j < n; j++)
            {
                bool flg = 0;
                for (int k = 0; k < m && !flg; k++)
                    for (int p = 0; p < 4; p++)
                        flg |= Cross_segment(ctrl[i], ctrl[j], detail[k * 4 + p], detail[k * 4 + (p + 1) % 4]);
                if (!flg)
                    bs2[i][j] = bs2[j][i] = 1;
            }
            for (int j = 0; j < m * 4; j++)
            {
                bool flg = 0;
                for (int k = 0; k < m && !flg; k++)
                    for (int p = 0; p < 4; p++)
                    {
                        auto p1 = detail[k * 4 + p], p2 = detail[k * 4 + (p + 1) % 4];
                        flg |= p1 != detail[j] && p2 != detail[j] && Cross_segment(ctrl[i], detail[j], p1, p2);
                    }
                if (!flg)
                    bs1[i][j] = 1;
            }
        }

        int ans = 2568;
        for (int i = 1; i < (1 << n); i++)
        {
            bitset<400> tmp1;
            bitset<20> tmp2;
            for (int j = 0; j < n; j++)
                if (i & (1 << j))
                    tmp1 |= bs1[j], tmp2 |= bs2[j];
            tmp2 &= i, tmp2 ^= i;
            if (tmp1.count() == m * 4 && tmp2.count() == 0)
                ans = min(ans, __builtin_popcount(i));
        }
        if (ans == 2568)
            cout << "No Solution!\n";
        else
            cout << ans << "\n";
    }

    return 0;
}

E. 3D Puzzles

Dancing Links X(舞蹈链)

没用


F. BBQ

DP

给队友了


G. Count Set

数学图论

给队友了


H. AC/DC

字符串数据结构

待补


I. Cube Rotate

数学

过难


J. Bragging Dice

博弈论

神必签到题,题意模糊。
正确题意如下:两个人各有 n n n 个骰子,并且已知所有骰子上的数。游戏轮流进行,本回合玩家可以声明【场上有 x x x 个骰子上的数是 y y y】,如果之前有玩家声明过,那他只能声明【场上有 x 1 x_1 x1 ( x 1 > x ) (x_1>x) (x1>x) 个骰子上的数是 y 1 y_1 y1 ( 1 ≤ y 1 ≤ 6 ) (1 \leq y_1 \leq 6) (1y16)】或者【场上有 x 2 x_2 x2 ( x 2 = x ) (x_2=x) (x2=x) 个骰子上的数是 y 2 y_2 y2 ( y 2 > y ) (y_2>y) (y2>y)】。他也可以选择质疑对手的声明,质疑成功则获胜,否则失败。有几条特殊规则,如果一个玩家的 n n n 个骰子点数各不相同,则无视这些骰子;如果一个玩家的 n n n 个骰子点数都相同,视为他还有一个同点数的骰子;否则,如果从来没有玩家声明过点数为 1 1 1 的骰子,则点数为 1 1 1 的骰子可以在声明中当任意点数使。

非公平博弈,假设所有点数最大出现次数为 m x mx mx,对应的数为 x 1 , ⋯   , x k x_1,\cdots,x_k x1,,xk ( x 1 < . . . < x k x_1<...<x_k x1<...<xk),如果先手直接声明【场上有 m x mx mx 个骰子上的数是 x k x_k xk】,则后手无法反制,因为既不能使用声明1,也不能使用声明2。
只有一种情况先手必败,即 m x = 0 mx=0 mx=0,因为不允许声明有 0 0 0 个骰子,此时对应情况为两个玩家各自的 n n n 个骰子上的数都不同,两人都符合特殊规则1,场上没有任何骰子,先手无法进行任何合法声明。

#include <bits/stdc++.h>
using namespace std;

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while (t--)
    {
        int n, x;
        cin >> n;
        bool flg = 1;
        for (int i = 1; i <= 2; i++)
        {
            int cnt[7] = {0};
            for (int j = 1; j <= n; j++)
            {
                cin >> x;
                if (++cnt[x] != 1)
                    flg = 0;
            }
        }
        cout << (!flg ? "Win!\n" : "Just a game of chance.\n");
    }

    return 0;
}

K. Kazuha’s String

字符串数学搜索暴力

本质上是一个群论问题,可以模拟六面体旋转群,然而难度不小,而且对群论一无所知。

实际上也可以认为是一个图论问题,以最短的字符串为根,生成(通过任意插入和删除对应子串)的所有串都处于同一连通块,且等价于根字符串。容易猜想连通块个数不会太多。
于是直接从小到大枚举字符串,暴力搜索,可以发现只有24个连通块。

在这里插入图片描述
虽然找到了连通块,但是每个块中只能爆搜到很小规模的字符串,从而在询问串 s s s 很长时,难以直接获取 r o o t [ s ] root[s] root[s]。注意到根字符串长度总是 ≤ 4 \leq 4 4,这意味着所有长度 ≥ 5 \geq 5 5 的串都能转换成长度 ≤ 4 \leq 4 4 的串,那么求出长度 ≤ 5 \leq 5 5 的字符串的转换关系,即可不断取出 s s s 的前缀字符,并最终转换得到根字符串。

所以其实最终打完表只有72个转换关系,直接写死在程序中即可省去打表复杂度,轻松通过。

#include <bits/stdc++.h>
using namespace std;

string op[] = {"aa", "bbb", "cccc", "abababab", "acacac", "bcbc", "abc"};
map<string, string> mp;
string rt;

void dfs(const string &cur)
{
    if (cur.size() > 11 || mp.count(cur))
        return;
    mp[cur] = rt;
    for (int i = 0; i < cur.size(); i++) // 删除
        for (const auto &s : op)
            if (cur.substr(i, s.size()) == s)
                dfs(cur.substr(0, i) + cur.substr(i + s.size()));
    for (int i = 0; i <= cur.size(); i++) // 插入
        for (const auto &s : op)
            dfs(cur.substr(0, i) + s + cur.substr(i));
}

void bfs()
{
    set<string> root;
    queue<string> q;
    q.push("");
    while (!q.empty())
    {
        auto s = q.front();
        q.pop();
        if (mp.count(s))
            continue;
        root.insert(s), rt = s;
        dfs(s);
        q.push(s + 'a'), q.push(s + 'b'), q.push(s + 'c');
    }
    for (const auto &s : root)
        for (char ch = 'a'; ch <= 'c'; ch++)
            printf("mp[\"%s%c\"] = \"%s\";\n", s.c_str(), ch, mp[s + ch].c_str());
}

void init()
{
    mp["a"] = "a";
    mp["b"] = "b";
    mp["c"] = "c";
    mp["aa"] = "";
    mp["ab"] = "ab";
    mp["ac"] = "ac";
    mp["aba"] = "aba";
    mp["abb"] = "abb";
    mp["abc"] = "";
    mp["abaa"] = "ab";
    mp["abab"] = "cc";
    mp["abac"] = "abac";
    mp["abaca"] = "ccb";
    mp["abacb"] = "cbac";
    mp["abacc"] = "acb";
    mp["abba"] = "ac";
    mp["abbb"] = "a";
    mp["abbc"] = "aba";
    mp["aca"] = "abb";
    mp["acb"] = "acb";
    mp["acc"] = "acc";
    mp["acba"] = "acba";
    mp["acbb"] = "acbb";
    mp["acbc"] = "abb";
    mp["acbaa"] = "acb";
    mp["acbab"] = "abac";
    mp["acbac"] = "bacb";
    mp["acbba"] = "acc";
    mp["acbbb"] = "ac";
    mp["acbbc"] = "acba";
    mp["acca"] = "acbb";
    mp["accb"] = "accb";
    mp["accc"] = "b";
    mp["accba"] = "bac";
    mp["accbb"] = "ba";
    mp["accbc"] = "acbb";
    mp["ba"] = "ba";
    mp["bb"] = "bb";
    mp["bc"] = "a";
    mp["baa"] = "b";
    mp["bab"] = "acc";
    mp["bac"] = "bac";
    mp["baca"] = "accb";
    mp["bacb"] = "bacb";
    mp["bacc"] = "cb";
    mp["bacba"] = "cbac";
    mp["bacbb"] = "cba";
    mp["bacbc"] = "accb";
    mp["bba"] = "c";
    mp["bbb"] = "";
    mp["bbc"] = "ba";
    mp["ca"] = "bb";
    mp["cb"] = "cb";
    mp["cc"] = "cc";
    mp["cba"] = "cba";
    mp["cbb"] = "cbb";
    mp["cbc"] = "bb";
    mp["cbaa"] = "cb";
    mp["cbab"] = "bac";
    mp["cbac"] = "cbac";
    mp["cbaca"] = "bacb";
    mp["cbacb"] = "acba";
    mp["cbacc"] = "ccb";
    mp["cbba"] = "cc";
    mp["cbbb"] = "c";
    mp["cbbc"] = "cba";
    mp["cca"] = "cbb";
    mp["ccb"] = "ccb";
    mp["ccc"] = "ab";
    mp["ccba"] = "abac";
    mp["ccbb"] = "aba";
    mp["ccbc"] = "cbb";
}

string f(const string &s)
{
    string res;
    for (auto ch : s)
        res = mp[res + ch];
    return res;
}

int main()
{
    // bfs();
    init();
    int T;
    cin >> T;
    while (T--)
    {
        string s, t;
        cin >> s >> t;
        cout << (f(s) == f(t) ? "yes\n" : "no\n");
    }

    return 0;
}

L. Buy Figurines

数据结构模拟

需要一个堆来维护每个队列的人数,为了方便实时修改可以使用set。对每个队列,另外记录其人数和最晚结束时间。将顾客按到达时间排序后,每次先把所有已经完成购买的顾客出队,因此还需要一个堆来维护当前所有顾客的结束时间,这之后再让当前顾客入队。其实只关心每个队列的最晚时间,因此出队入队不需要队列数据结构,直接维护时间即可。

#include <bits/stdc++.h>
using namespace std;

using PII = pair<int64_t, int64_t>;
constexpr int MAXN = 2e5 + 5;
PII vec[MAXN];
int64_t sz[MAXN], lst[MAXN];

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int T;
    cin >> T;
    while (T--)
    {
        int64_t ans = 0;
        int n, m;
        cin >> n >> m;
        set<PII> S, del;
        for (int i = 1; i <= n; i++)
            cin >> vec[i].first >> vec[i].second;
        sort(vec + 1, vec + n + 1);
        for (int i = 1; i <= m; i++)
            S.emplace(0, i), sz[i] = lst[i] = 0;
        for (int i = 1; i <= n; i++)
        {
            auto [a, s] = vec[i];
            while (!del.empty())
            {
                auto [t, id] = *del.begin();
                if (t > a)
                    break;
                del.erase(del.begin());
                S.erase({sz[id], id}), S.emplace(--sz[id], id);
            }
            auto [_, id] = *S.begin();
            S.erase(S.begin()), S.emplace(++sz[id], id);
            if (lst[id] <= a)
                lst[id] = a + s;
            else
                lst[id] += s;
            del.emplace(lst[id], id);
            ans = max(ans, lst[id]);
        }
        cout << ans << "\n";
    }

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NoobDream_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值