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

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

难度评价

数据结构场,各种线段树维护应有尽有。
7题一百名,5题快两百名,4题快四百名。有手就能写4到5题,起码补到7题,金牌补到9题以上。
B、G、I、L签到,C数据有锅掉成签到,A、H、K中等,D、E、F困难,J大难


A. Static Query on Tree

数据结构

要求既能从集合A、B內一点出发到达,又能走到集合C內一点的结点数量,显然这些点一定在c子树內,且是a、b的祖先。于是进行树剖,对集合A、B內每个点,其到根结点的路径都打上a/b标记;对集合C內每个点,其子树內所有点打上c标记。问题转换为怎么用线段树维护同时有三种标记的点的数量。
因为是区间操作,而贡献来自单点,不可能让修改暴力递归至单点,可以考虑将标记表示为二进制形式,如三种标记都有为111,则我们需要维护8种标记情况的结点数量,具体写起来很复杂。

#include <bits/stdc++.h>
using namespace std;
#define ls rt << 1
#define rs rt << 1 | 1

constexpr int MAXN = 2e5 + 5;
int sz[MAXN], dep[MAXN], father[MAXN], son[MAXN], dfn, n, q;
int top[MAXN], num[MAXN], rnk[MAXN], bottom[MAXN];
int sum[MAXN << 2], cnt[MAXN << 2][8], tag[MAXN << 2], clr[MAXN << 2];
vector<int> G[MAXN];

void dfs1(int v, int fa)
{
    dep[v] = dep[fa] + 1;
    sz[v] = 1;
    son[v] = 0;
    father[v] = fa;
    for (auto u : G[v])
    {
        if (u == fa)
            continue;
        dfs1(u, v);
        sz[v] += sz[u];
        if (sz[u] > sz[son[v]])
            son[v] = u;
    }
}

void dfs2(int v, int tp)
{
    top[v] = tp;
    num[v] = ++dfn;
    bottom[v] = num[v];
    rnk[dfn] = v;
    if (son[v] == 0)
        return;
    dfs2(son[v], tp);
    for (auto u : G[v])
        if (u != son[v] && u != father[v])
            dfs2(u, u);
    bottom[v] = dfn;
}

void pushUp(int rt)
{
    sum[rt] = sum[ls] + sum[rs];
    for (int i = 0; i < 8; i++)
        cnt[rt][i] = cnt[ls][i] + cnt[rs][i];
}

void pushDown(int rt, int ln, int rn)
{
    if (clr[rt])
    {
        clr[ls] = clr[rs] = 1;
        tag[ls] = tag[rs] = 0;
        sum[ls] = sum[rs] = 0;
        cnt[ls][0] = ln, cnt[rs][0] = rn;
        for (int i = 1; i < 8; i++)
            cnt[ls][i] = cnt[rs][i] = 0;
        clr[rt] = 0;
    }
    if (tag[rt])
    {
        tag[ls] |= tag[rt], tag[rs] |= tag[rt];
        for (int i = 0; i < 3; i++)
        {
            if (tag[ls] & (1 << i))
            {
                int tmp[8] = {0};
                for (int j = 1; j < 8; j++)
                    if (j & (1 << i))
                        tmp[j] = cnt[ls][j] + cnt[ls][j ^ (1 << i)];
                for (int j = 0; j < 8; j++)
                    cnt[ls][j] = tmp[j];
            }
            if (tag[rs] & (1 << i))
            {
                int tmp[8] = {0};
                for (int j = 1; j < 8; j++)
                    if (j & (1 << i))
                        tmp[j] = cnt[rs][j] + cnt[rs][j ^ (1 << i)];
                for (int j = 0; j < 8; j++)
                    cnt[rs][j] = tmp[j];
            }
        }
        sum[ls] = cnt[ls][7], sum[rs] = cnt[rs][7];
        tag[rt] = 0;
    }
}

void build(int l, int r, int rt)
{
    if (l == r)
    {
        cnt[rt][0] = 1;
        return;
    }
    int mid = (l + r) >> 1;
    build(l, mid, ls);
    build(mid + 1, r, rs);
    pushUp(rt);
}

void modify(int L, int R, int C, int l, int r, int rt)
{
    if (L <= l && r <= R)
    {
        tag[rt] |= C;
        for (int i = 0; i < 3; i++)
        {
            if (tag[rt] & (1 << i))
            {
                int tmp[8] = {0};
                for (int j = 1; j < 8; j++)
                    if (j & (1 << i))
                        tmp[j] = cnt[rt][j] + cnt[rt][j ^ (1 << i)];
                for (int j = 0; j < 8; j++)
                    cnt[rt][j] = tmp[j];
            }
        }
        sum[rt] = cnt[rt][7];
        return;
    }
    int mid = (l + r) >> 1;
    pushDown(rt, mid - l + 1, r - mid);

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

void pathModify(int v, int u, int z)
{
    while (top[v] != top[u])
    {
        if (dep[top[v]] < dep[top[u]])
            swap(v, u);
        modify(num[top[v]], num[v], z, 1, n, 1);
        v = father[top[v]];
    }
    if (num[v] > num[u])
        swap(v, u);
    modify(num[v], num[u], z, 1, n, 1);
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while (t--)
    {
        cin >> n >> q;
        for (int i = 2, f; i <= n; i++)
            cin >> f, G[f].push_back(i);
        dfs1(1, 0);
        dfs2(1, 1);
        build(1, n, 1);
        while (q--)
        {
            int a, b, c, x;
            cin >> a >> b >> c;
            while (a--)
                cin >> x, pathModify(1, x, 1);
            while (b--)
                cin >> x, pathModify(1, x, 2);
            while (c--)
                cin >> x, modify(num[x], bottom[x], 4, 1, n, 1);
            cout << sum[1] << "\n";
            clr[1] = 1, tag[1] = sum[1] = 0, cnt[1][0] = n;
            for (int i = 1; i < 8; i++)
                cnt[1][i] = 0;
        }

        for (int i = 1; i <= n; i++)
            G[i].clear();
        dfn = 0;
    }

    return 0;
}

B. C++ to Python

模拟

只输出数字、括号、逗号、负号即可。

#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--)
    {
        string s;
        cin >> s;
        for (auto c : s)
            if (isdigit(c) || c == '(' || c == ')' || c == '-' || c == ',')
                cout << c;
        cout << endl;
    }

    return 0;
}

C. Copy

暴力

由于杭电特性,限制了数据总和又只能上传一个测试点文件,导致暴力可过。暴力做法如下,对每次询问,倒序遍历所有复制操作 [ l , r ] [l,r] [l,r],如果 x ≤ r x \leq r xr,那么实际上没有被这次操作影响,否则查询的位置是 x − ( r − l + 1 ) x-(r-l+1) x(rl+1),复杂度 O ( N 2 ) O(N^2) O(N2)

注意到答案询问异或和,如果知道所有 N N N 个数中哪些被询问奇数次,最后遍历一次即可求出答案。
正解考虑优化暴力,离线所有询问和操作,倒序处理。对于一个询问,它还是需要考虑之前的所有操作,但是我们只关心最终哪些数被询问奇数次,所以我们可以同时处理一堆询问。用 b s [ x ] bs[x] bs[x] 维护 x x x 是否被询问奇数次,遇到询问直接令 b s [ x ] ⊕ 1 bs[x] \oplus 1 bs[x]1;遇到操作 [ l , r ] [l,r] [l,r],让所有满足 x > r x>r x>r 的位置都往前移动 r − l + 1 r-l+1 rl+1,这时会有一些询问重叠( b s [ x ] ⊕ b s [ x − ( r − l + 1 ) ] = 0 bs[x] \oplus bs[x-(r-l+1)]=0 bs[x]bs[x(rl+1)]=0),则它们变为出现偶数次,其余的就是出现奇数次。整个过程可以用bitset优化移位和异或,复杂度 O ( N 2 w ) O(\cfrac{N^2}{w}) O(wN2)

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

constexpr int MAXN = 1e5 + 5;
int a[MAXN];
bitset<MAXN> bs, all;

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while (t--)
    {
        int n, q;
        cin >> n >> q;
        for (int i = 1; i <= n; i++)
            cin >> a[i], all[i] = 1;
        vector<tuple<int, int, int>> qry(q);
        for (auto &[op, l, r] : qry)
        {
            cin >> op >> l;
            if (op == 1)
                cin >> r;
        }
        for (int i = q - 1; i >= 0; i--)
        {
            auto [op, l, r] = qry[i];
            if (op == 2)
                bs.flip(l);
            else
            {
                auto low = bs & (all >> (n - r)), high = bs & (all << r);
                bs = low ^ (high >> (r - l + 1));
            }
        }

        int ans = 0;
        for (int i = 1; i <= n; i++)
            if (bs[i])
                ans ^= a[i];
        cout << ans << "\n";
        bs.reset(), all.reset();
    }

    return 0;
}

D. Keychains

计算几何

待补


E. Slayers Come

DP数据结构

每个技能可以杀死一个区间的怪物,求有多少种方案让所有怪至少死一次。本题只有两个关键,一是求出所有技能对应的区间,二是求方案数。
以求区间右端点为例,每当 a [ j ] − b [ j + 1 ] ≥ R [ i ] a[j]-b[j+1] \geq R[i] a[j]b[j+1]R[i] 时,往右扩展一个怪物,注意到对于 R [ i ] ≥ R [ i ′ ] R[i] \geq R[i'] R[i]R[i],一定有右端点 r [ i ] ≤ r [ i ′ ] r[i] \leq r[i'] r[i]r[i],即 i i i 能杀的怪, i ′ i' i 也一定能杀。那么,只要将技能按 R R R 从大到小排序,使用并查集优化扩展过程即可,如果更小的 R R R 找到了一个区间,可以直接跳到区间右端点再继续扩展。求左端点同理。当然,由于这个问题相当于在 [ x , n ] [x,n] [x,n] 上找第一个 a [ p ] − b [ p + 1 ] < R [ i ] a[p]-b[p+1]<R[i] a[p]b[p+1]<R[i],也可以用线段树二分之类的办法做到,无脑但是麻烦。
求方案数是一个区间重复覆盖问题。考虑DP,令 d p [ i ] dp[i] dp[i] 表示恰好覆盖区间 [ 1 , i ] [1,i] [1,i] 的方案数,区间按右端点从小到大排序。对于当前区间 [ l , r ] [l,r] [l,r],有 d p [ r ] + = ∑ i = l − 1 r d p [ i ] dp[r]+=\sum_{i=l-1}^rdp[i] dp[r]+=i=l1rdp[i],因为这个新区间可选可不选,对所有 0 ≤ i ≤ l − 2 0 \leq i \leq l-2 0il2 d p [ i ] ∗ = 2 dp[i]*=2 dp[i]=2。因为需要区间操作,可以用线段树维护。

#include <bits/stdc++.h>
using namespace std;
#define ls rt << 1
#define rs rt << 1 | 1

constexpr int64_t MAXN = 1e5 + 10, mod = 998244353;
int a[MAXN], b[MAXN], dsu[MAXN];
int64_t sum[MAXN << 2], tag[MAXN << 2];
struct Info
{
    int x, L, R, l, r;
} s[MAXN];

int find(int x) { return x == dsu[x] ? x : dsu[x] = find(dsu[x]); }

void pushUp(int rt) { sum[rt] = (sum[ls] + sum[rs]) % mod; }

void pushDown(int rt)
{
    if (tag[rt] != 1)
    {
        tag[ls] = tag[ls] * tag[rt] % mod, tag[rs] = tag[rs] * tag[rt] % mod;
        sum[ls] = sum[ls] * tag[rt] % mod, sum[rs] = sum[rs] * tag[rt] % mod;
        tag[rt] = 1;
    }
}

void add(int L, int64_t C, int l, int r, int rt)
{
    if (l == r)
    {
        sum[rt] = (sum[rt] + C) % mod;
        return;
    }
    int mid = (l + r) >> 1;
    pushDown(rt);

    if (L <= mid)
        add(L, C, l, mid, ls);
    else
        add(L, C, mid + 1, r, rs);
    pushUp(rt);
}

void mul(int L, int R, int l, int r, int rt)
{
    if (L <= l && r <= R)
    {
        sum[rt] = sum[rt] * 2 % mod;
        tag[rt] = tag[rt] * 2 % mod;
        return;
    }
    int mid = (l + r) >> 1;
    pushDown(rt);

    if (L <= mid)
        mul(L, R, l, mid, ls);
    if (R > mid)
        mul(L, R, mid + 1, r, rs);
    pushUp(rt);
}

int64_t query(int L, int R, int l, int r, int rt)
{
    if (L <= l && r <= R)
        return sum[rt];
    int mid = (l + r) >> 1;
    pushDown(rt);

    int64_t ans = 0;
    if (L <= mid)
        ans = (ans + query(L, R, l, mid, ls)) % mod;
    if (R > mid)
        ans = (ans + query(L, R, mid + 1, r, rs)) % mod;
    return ans;
}

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;
        for (int i = 1; i <= n; i++)
            cin >> a[i] >> b[i];
        for (int i = 1; i <= m; i++)
            cin >> s[i].x >> s[i].L >> s[i].R;
        sort(s + 1, s + m + 1, [](const Info &lhs, const Info &rhs)
             { return lhs.R > rhs.R; });
        iota(dsu + 1, dsu + n + 1, 1);
        for (int i = 1; i <= m; i++)
        {
            int r = find(s[i].x);
            while (r < n && a[r] - b[r + 1] >= s[i].R)
                dsu[r] = find(r + 1), r = dsu[r];
            s[i].r = r;
        }
        sort(s + 1, s + m + 1, [](const Info &lhs, const Info &rhs)
             { return lhs.L > rhs.L; });
        iota(dsu + 1, dsu + n + 1, 1);
        for (int i = 1; i <= m; i++)
        {
            int l = find(s[i].x);
            while (l > 1 && a[l] - b[l - 1] >= s[i].L)
                dsu[l] = find(l - 1), l = dsu[l];
            s[i].l = l;
        }

        sort(s + 1, s + m + 1, [](const Info &lhs, const Info &rhs)
             { return lhs.r < rhs.r; });
        fill(sum + 1, sum + n * 2 + 5, 0);
        fill(tag + 1, tag + n * 2 + 5, 0);
        add(0, 1, 0, n, 1); // dp[0] = 1;
        for (int i = 1; i <= m; i++)
        {
            int l = s[i].l, r = s[i].r;
            add(r, query(l - 1, r, 0, n, 1), 0, n, 1);
            if (l >= 2)
                mul(0, l - 2, 0, n, 1);
        }
        cout << query(n, n, 0, n, 1) << "\n";
    }

    return 0;
}

F. Bowcraft

DP数学

过难


G. Snatch Groceries

排序

题面巨长,聪明人只从倒数第三段开始读,题意实际上就是问从左到右,第一次与后面区间重叠(端点也算)的区间的下标(从0开始),如果没有,答案是 n n n。显然排序一下比较端点就行了。

#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;
        cin >> n;
        vector<pair<int, int>> vec(n);
        for (auto &[l, r] : vec)
            cin >> l >> r;
        stable_sort(vec.begin(), vec.end()); // 用sort会莫名其妙不让提交
        int ans = n;
        for (int i = 0; i < n - 1; i++)
        {
            if (vec[i].second >= vec[i + 1].first)
            {
                ans = i;
                break;
            }
        }
        cout << ans << endl;
    }

    return 0;
}

H. Keyboard Warrior

字符串数据结构

询问是否存在一个时刻,目标串是当前串的一个子串。可以在每次两串结尾字符相同时检查一次,通过哈希完成。因为当前串很长,考虑用一个栈压缩存储相同字符。问题主要在于怎么维护当前串的哈希值,如果使用平常的哈希函数,即 h = ( ∑ i = 0 n s [ i ] ∗ b a s e n − i ) % m o d h=(\sum_{i=0}^{n}s[i]*base^{n-i})\%mod h=(i=0ns[i]baseni)%mod,由于是压缩字符串,计算起来还是比较麻烦的。
考虑随机权值, v a l [ c h ] = r a n d ( ) val[ch]=rand() val[ch]=rand() h = ( ∑ i = 0 n v a l [ s [ i ] ] ) % m o d h=(\sum_{i=0}^{n}val[s[i]])\%mod h=(i=0nval[s[i]])%mod,显然算当前串哈希值很方便,并且实际上足以避免冲突(如果不放心,本题时限甚至足以四哈希)。于是直接模拟操作,一边维护当前串的字符栈、长度、长度前缀和、哈希前缀和,一边在尾字符相同时检查。检查时可以单独考虑首尾,这里会遇到一些细节。

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

constexpr int MAXN = 2e6 + 5, mod = 1e9 + 7;
mt19937 mt(chrono::system_clock::now().time_since_epoch().count());
int64_t h[MAXN], len[MAXN], lensum[MAXN], val[200];
char st[MAXN];

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    for (int i = 'a'; i <= 'z'; i++)
        val[i] = mt() % mod;
    for (int i = '0'; i <= '9'; i++)
        val[i] = mt() % mod;
    int t;
    cin >> t;
    while (t--)
    {
        int n, m, ok = 0;
        string s;
        cin >> n >> m >> s;
        int cntl = 0, cntr = 0;
        for (int i = 0; i < n && s[i] == s[0]; i++)
            ++cntl;
        for (int i = n - 1; i >= 0 && s[i] == s.back(); i--)
            ++cntr;
        int64_t h0 = 0;
        for (int i = cntl; i < n - cntr; i++)
            h0 = (h0 + val[s[i]]) % mod;

        int cur = 0;
        while (m--)
        {
            char ch;
            int k;
            cin >> ch >> k;
            if (k == 0)
                continue;
            if (ch == '-')
            {
                if (cur == 0 || k >= lensum[cur])
                {
                    cur = 0;
                    continue;
                }
                int l = lower_bound(lensum + 1, lensum + cur + 1, lensum[cur] - k) - lensum;
                auto del = lensum[l] - (lensum[cur] - k);
                cur = l, lensum[cur] -= del, len[cur] -= del;
            }
            else if (cur == 0 || st[cur] != ch)
            {
                st[++cur] = ch;
                len[cur] = k, lensum[cur] = lensum[cur - 1] + k;
            }
            else
            {
                len[cur] += k, lensum[cur] += k;
            }
            h[cur] = (h[cur - 1] + val[st[cur]] * len[cur] % mod) % mod;

            if (st[cur] == s.back() && lensum[cur] >= n && len[cur] >= cntr)
            {
                int l = upper_bound(lensum + 1, lensum + cur + 1, lensum[cur - 1] - (n - cntr)) - lensum;
                if (len[l] < cntl)
                    continue;
                if (l == cur && cntl == n)
                    ok = 1;
                else if (h0 == (h[cur - 1] - h[l] + mod) % mod)
                    ok = 1;
            }
        }
        cout << (ok ? "yes\n" : "no\n");
    }

    return 0;
}

I. ShuanQ

数学

注意到 p × q ≡ 1   ( m o d   M ) p \times q \equiv 1 \space (mod \space M) p×q1 (mod M),即 p × q − 1 = k m p \times q-1=km p×q1=km,又 M M M 是质数, M > m a x ( p , q ) M > max(p,q) M>max(p,q),所以 M M M p × q − 1 p \times q-1 p×q1 的一个大于 m a x ( p , q ) max(p,q) max(p,q) 的质因数。如果能找到,那么一定有且仅有一个(因为 M > m a x ( p , q ) M>max(p,q) M>max(p,q)),否则无解。

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

map<int64_t, int> getPrimeFactor(int64_t a)
{
    map<int64_t, int> mp;
    for (int64_t i = 2; i * i <= a; i++)
    {
        if (a <= 1)
            break;
        while (a % i == 0)
            a /= i, ++mp[i];
    }
    if (a > 1)
        mp[a]++;
    return mp;
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while (t--)
    {
        int p, q, d;
        int64_t ans = -1;
        cin >> p >> q >> d;
        auto mp = getPrimeFactor(1ll * p * q - 1);
        for (auto [v, _] : mp)
            if (v > max(p, q))
                ans = 1ll * d * q % v;

        if (ans != -1)
            cout << ans << "\n";
        else
            cout << "shuanQ\n";
    }

    return 0;
}

J. Assassination

图论

要求删除最少的边,使得新图中不存在原图的任何一棵最大生成树。显然有两种情况,一是整张图不连通,二是最大生成树权值减小。
我们考虑kruskal求生成树的过程,假设已处理了所有 > w >w >w 的边,根据生成树理论,此时图中并查集连通性唯一确定。现在要加入权值 = w =w =w 的边,如果破坏即将构造的连通性,要么即使加入后续边也无法连通整张图,要么加入后续边后生成树权值减小。
因此将当前所有连通块缩点,所有权值 = w =w =w 的边连接这些点(连接连通块内部点的边不用考虑),对这个图跑最小割,答案对所有 w w w 对应的最小割取最小值即可。

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

constexpr int MAXN = 1e5 + 5, MAXM = 2e6 + 5;
set<int, greater<int>> sw;
vector<pair<int, int>> edge[MAXN];
int n, m, s = 1e5 + 1, t = 1e5 + 2, maxflow, d[MAXN], fa[MAXN];
int head[MAXN], tot = 1;
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;
    e[++tot].to = u, e[tot].w = 0, e[tot].next = head[v], head[v] = tot;
}

bool bfs(const vector<int> &ver)
{
    for (auto v : ver)
        d[v] = 0;
    d[s] = 1, d[t] = 0;
    queue<int> q;
    q.push(s);
    while (!q.empty())
    {
        int v = q.front();
        q.pop();
        for (int i = head[v]; i; i = e[i].next)
        {
            int u = e[i].to, w = e[i].w;
            if (w && !d[u])
            {
                q.push(u);
                d[u] = d[v] + 1;
                if (u == t)
                    return 1;
            }
        }
    }
    return 0;
}

int dinic(int v, int flow)
{
    if (v == t)
        return flow;
    int rest = flow;
    for (int i = head[v]; i && rest; i = e[i].next)
    {
        int u = e[i].to, w = e[i].w;
        if (w && d[u] == d[v] + 1)
        {
            int k = dinic(u, min(rest, w));
            if (!k)
                d[u] = 0;
            else
                e[i].w -= k, e[i ^ 1].w += k, rest -= k;
        }
    }
    return flow - rest;
}

int findfa(int x) { return x == fa[x] ? x : fa[x] = findfa(fa[x]); }

int kruskal()
{
    int ans = 1e9;
    iota(fa + 1, fa + n + 1, 1);
    fill(head + 1, head + n + 1, 0), tot = 1;
    for (auto w : sw)
    {
        vector<int> ver;
        for (auto [v, u] : edge[w])
        {
            int fav = findfa(v), fau = findfa(u);
            if (fav != fau)
                add(fav, fau, 1), add(fau, fav, 1), ver.push_back(fav), ver.push_back(fau);
        }
        if (ver.empty())
            continue;

        int flow = 0;
        add(s, ver[0], 1e9), add(ver[1], t, 1e9);
        while (bfs(ver))
            while (flow = dinic(s, 1e9))
                maxflow += flow;
        ans = min(ans, maxflow), maxflow = 0;
        for (auto [v, u] : edge[w])
            fa[findfa(v)] = fa[findfa(u)];

        tot = 1;
        for (auto v : ver)
            head[v] = 0;
        head[s] = head[t] = 0;
    }
    return ans;
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int T;
    cin >> T;
    while (T--)
    {
        cin >> n >> m;
        for (int i = 1; i <= m; i++)
        {
            int v, u, w;
            cin >> v >> u >> w;
            edge[w].emplace_back(v, u), sw.insert(w);
        }
        cout << kruskal() << "\n";
        for (auto w : sw)
            edge[w].clear();
        sw.clear();
    }

    return 0;
}

K. DOS Card

数据结构

一眼线段树上维护区间信息求解,但是并不知道维护什么。注意到题目实际上是要求找出两对互不相同的 i < j i<j i<j ,让 a [ i ] 2 − a [ j ] 2 a[i]^2-a[j]^2 a[i]2a[j]2 之和最大。变成如上形式后无需考虑配对,只有两种情况, + + − − ++-- ++ + − + − +-+- ++,于是可以用线段树区间合并维护所有信息最大值。

p p p (plus) 代表 + + + s s s (sub) 代表 − - ,维护方法如下:

  • p p p s s s 单点已知,区间取最值
  • p p = m a x ( l . p p , r . p p , l . p + r . p ) pp=max(l.pp,r.pp,l.p+r.p) pp=max(l.pp,r.pp,l.p+r.p) p s = m a x ( l . p s , r . p s , l . p + r . s ) ps=max(l.ps,r.ps,l.p+r.s) ps=max(l.ps,r.ps,l.p+r.s) s p = m a x ( l . s p , r . s p , l . s + r . p ) sp=max(l.sp,r.sp,l.s+r.p) sp=max(l.sp,r.sp,l.s+r.p) s s = m a x ( l . s s , r . s s , l . s + r . s ) ss=max(l.ss,r.ss,l.s+r.s) ss=max(l.ss,r.ss,l.s+r.s)
  • p p s = m a x ( l . p p s , r . p p s , l . p p + r . s , l . p + r . p s ) pps=max(l.pps,r.pps,l.pp+r.s,l.p+r.ps) pps=max(l.pps,r.pps,l.pp+r.s,l.p+r.ps) p s s = m a x ( l . p s s , r . p s s , l . p s + r . s , l . p + r . s s ) pss=max(l.pss,r.pss,l.ps+r.s,l.p+r.ss) pss=max(l.pss,r.pss,l.ps+r.s,l.p+r.ss) p s p = m a x ( l . p s p , r . p s p , l . p s + r . p , l . p + r . s p ) psp=max(l.psp,r.psp,l.ps+r.p,l.p+r.sp) psp=max(l.psp,r.psp,l.ps+r.p,l.p+r.sp) s p s = m a x ( l . s p s , r . s p s , l . s p + r . s , l . s + r . p s ) sps=max(l.sps,r.sps,l.sp+r.s,l.s+r.ps) sps=max(l.sps,r.sps,l.sp+r.s,l.s+r.ps)
  • p p s s = m a x ( l . p p s s , r . p p s s , l . p p s + r . s , l . p p + r . s s , l . p + r . p s s ) ppss=max(l.ppss,r.ppss,l.pps+r.s,l.pp+r.ss,l.p+r.pss) ppss=max(l.ppss,r.ppss,l.pps+r.s,l.pp+r.ss,l.p+r.pss) p s p s = m a x ( l . p s p s , r . p s p s , l . p s p + r . s , l . p s + r . p s , l . p + r . s p s ) psps=max(l.psps,r.psps,l.psp+r.s,l.ps+r.ps,l.p+r.sps) psps=max(l.psps,r.psps,l.psp+r.s,l.ps+r.ps,l.p+r.sps)
#include <bits/stdc++.h>
using namespace std;

constexpr int MAXN = 2e5 + 5;
struct Node
{
    int64_t p, s, pp, ps, sp, ss, pps, pss, psp, sps, ppss, psps;
    Node() { p = s = pp = ps = sp = ss = pps = pss = psp = sps = ppss = psps = -1e18; }
} tr[MAXN << 2];
int64_t a[MAXN];

void pushUp(Node &rt, const Node &l, const Node &r)
{
    rt.p = max(l.p, r.p), rt.s = max(l.s, r.s);
    rt.pp = max({l.pp, r.pp, l.p + r.p}), rt.ps = max({l.ps, r.ps, l.p + r.s}),
    rt.sp = max({l.sp, r.sp, l.s + r.p}), rt.ss = max({l.ss, r.ss, l.s + r.s});
    rt.pps = max({l.pps, r.pps, l.pp + r.s, l.p + r.ps}), rt.pss = max({l.pss, r.pss, l.ps + r.s, l.p + r.ss}),
    rt.psp = max({l.psp, r.psp, l.ps + r.p, l.p + r.sp}), rt.sps = max({l.sps, r.sps, l.sp + r.s, l.s + r.ps});
    rt.ppss = max({l.ppss, r.ppss, l.pps + r.s, l.pp + r.ss, l.p + r.pss}),
    rt.psps = max({l.psps, r.psps, l.psp + r.s, l.ps + r.ps, l.p + r.sps});
}

void build(int l, int r, int rt)
{
    if (l == r)
    {
        tr[rt] = Node();
        tr[rt].p = a[l], tr[rt].s = -a[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(l, mid, rt << 1);
    build(mid + 1, r, rt << 1 | 1);
    pushUp(tr[rt], tr[rt << 1], tr[rt << 1 | 1]);
}

Node query(int L, int R, int l, int r, int rt)
{
    if (L <= l && r <= R)
        return tr[rt];
    int mid = (l + r) >> 1;

    if (R <= mid)
        return query(L, R, l, mid, rt << 1);
    else if (L > mid)
        return query(L, R, mid + 1, r, rt << 1 | 1);
    else
    {
        Node res;
        pushUp(res, query(L, R, l, mid, rt << 1), query(L, R, mid + 1, r, rt << 1 | 1));
        return res;
    }
}

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;
        for (int i = 1; i <= n; i++)
            cin >> a[i], a[i] *= a[i];
        build(1, n, 1);
        while (m--)
        {
            int l, r;
            cin >> l >> r;
            auto res = query(l, r, 1, n, 1);
            cout << max(res.ppss, res.psps) << "\n";
        }
    }

    return 0;
}

L. Luxury cruise ship

贪心暴力DP

显然在 n n n 很大的时候,一直用365填充最优。当达到某个值的时候,开始跑完全背包,不妨设定为 1 e 5 1e5 1e5,反正不会T。

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

int dp[100500], w[3] = {7, 31, 365};

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    memset(dp, 0x3f, sizeof(dp));
    dp[0] = 0;
    for (int i = 0; i < 3; i++)
        for (int j = w[i]; j < 100500; j++)
            dp[j] = min(dp[j], dp[j - w[i]] + 1);
    int t;
    cin >> t;
    while (t--)
    {
        int64_t n, res = 0;
        cin >> n;
        if (n >= 100000)
            res += (n - 100000) / 365, n -= (n - 100000) / 365 * 365;
        cout << (dp[n] == 0x3f3f3f3f ? -1 : res + dp[n]) << "\n";
    }

    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

NoobDream_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值