跳转至

ABC362(A-G)

A - Buy a Pen

题目大意

有三种颜色的笔,Red\(R\) 元,Green\(G\) 元,Blue\(B\) 元。你不想购买颜色为 \(C\) 的笔,问:买一只笔最小需要多少钱?

参考代码
#include <iostream>

using namespace std;

int main()
{
    int r, g, b;
    string s;
    cin >> r >> g >> b;
    cin >> s;
    if(s == "Red")
        cout << min(g, b) << endl;
    else if(s == "Green")
        cout << min(r, b) << endl;
    else
        cout << min(r, g) << endl;
    return 0;
}

B - Right Triangle

题目大意

平面上有三个点 \(A(x_A, y_A)\)\(B(x_B, y_B)\)\(C(x_C, y_C)\),问:这三个点能否构成直角三角形?

参考代码
#include <iostream>

using namespace std;

struct Vector {
    int x, y;
    Vector(int x1, int y1, int x2, int y2) : x(x1-x2), y(y1-y2) {}
    int operator * (const Vector &other) const
    {return x * other.x + y * other.y;}
};

int main()
{
    int xa, xb, xc, ya, yb, yc;
    cin >> xa >> ya >> xb >> yb >> xc >> yc;
    Vector ab(xa, ya, xb, yb), ac(xa, ya, xc, yc), bc(xb, yb, xc, yc);
    if(ab * ac == 0 || ab * bc == 0 || ac * bc == 0)
        cout << "Yes" << endl;
    else
        cout << "No" << endl;
    return 0;
}

C - Sum = 0

题目大意

给你 \(N(1 \le N \le 2 \times 10 ^ 5)\) 个区间 \([L_1, R_1], [L_2, R_2], \cdots, [L_N, R_N]\),判断是否存在 \(N\) 个数的序列 \(X = (X_1, X_2, \cdots, X_N)\) 满足以下条件:

  • \(L_i \le X_i \le R_i\)
  • \(\sum\limits_{i = 1} ^ N X_i = 0\)

如果存在这样的序列则输出,否则输出 No

解题思路

把所有区间的上界和下界加起来,如果 \(\sum\limits_{i = 1} ^ N L_i \le 0 \le \sum\limits_{i = 1} ^ N R_i\) 说明有解,下面考虑如何构造答案。

预处理一个后缀和 \(SL_i = SL_{i+1} + L_i\)\(SR_i = SR_{i+1} + R_i\),同时用 \(sum\) 表示当前已经构造出来的 \(X_i\) 的和。

考虑一种解的方案:当某个 \(X_i\) 存在多个可以凑成 \(0\) 的解时,选最小的一个,因此,遵循以下规则构造解:

  • 如果 \(SL_i + sum \le 0 \le SR_i + sum\),此时令 \(X_i = L_i\) 即可。
  • 否则,我们需要让 \(SL_{i+1} + sum + X_i \le 0 \le SR_{i+1} + sum + X_i\),解得 \(-SR_{i+1} - sum \le X_i \le -SL_{i+1} - sum\),选择最小的解,令 \(X_i = -SR_{i+1} - sum\) 即可。
参考代码
#include <iostream>
#include <vector>

using namespace std;

using LL = long long;

int main()
{
    int n;
    cin >> n;
    vector<LL> L(n), R(n);
    for(int i = 0; i < n; i++)
        cin >> L[i] >> R[i];
    vector<LL> SL(n), SR(n);
    SL[n-1] = L[n-1];
    SR[n-1] = R[n-1];
    for(int i = n - 2; i >= 0; i--)
    {
        SL[i] = SL[i+1] + L[i];
        SR[i] = SR[i+1] + R[i];
    }
    if(SL[0] > 0 || SR[0] < 0)
    {
        cout << "No" << endl;
        return 0;
    }
    cout << "Yes" << endl;
    LL sum = 0;
    for(int i = 0; i < n - 1; i++)
    {
        if(SL[i+1] + sum + L[i] <= 0 && 0 <= SR[i + 1] + sum + L[i])
        {
            cout << L[i] << ' ';
            sum += L[i];
        }
        else
        {
            LL x = -sum - SR[i+1];
            cout << x << ' ';
            sum += x;
        }
    }
    cout << -sum << endl;
    return 0;
}

D - Shortest Path 3

题目大意

给你一个无向图,图中有点权和边权,定义一条路径的距离为路径上所有点权和边权的和。求点 \(1\) 到所有其他点的最短路、

参考代码
#include <iostream>
#include <queue>
#include <cstring>

using namespace std;
using LL = long long;

const int maxn = 2E5 + 10;
const int maxm = maxn * 2;
int head[maxn], edge[maxm], ne[maxm], cost[maxm], A[maxn], idx = 1;
void add(int u, int v, int w)
{
    edge[idx] = v;
    ne[idx] = head[u];
    cost[idx] = w;
    head[u] = idx++;
}

struct Node
{
    int u;
    LL dis;
    bool operator > (const Node &other) const
    {return dis > other.dis;}
};

LL dis[maxn];

void dijkstra()
{
    memset(dis, 0x3f, sizeof dis);
    priority_queue<Node, vector<Node>, greater<Node>> heap;
    dis[1] = A[1];
    heap.push({1, A[1]});
    while(!heap.empty())
    {
        auto [u, d] = heap.top();
        heap.pop();
        if(d > dis[u])
            continue;
        for(int i = head[u]; i; i = ne[i])
        {
            int v = edge[i];
            LL nd = d + cost[i] + A[v];
            if(nd < dis[v])
            {
                dis[v] = nd;
                heap.push({v, nd});
            }
        }
    }
}

int main()
{
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
        cin >> A[i];
    while(m--)
    {
        int u, v, w;
        cin >> u >> v >> w;
        add(u, v, w);
        add(v, u, w);
    }
    dijkstra();
    for(int i = 2; i <= n; i++)
        cout << dis[i] << ' ';
    return 0;
}

E - Count Arithmetic Subsequences

题目大意

给你 \(N(1 \le N \le 80)\) 个数 \(A = (A_1, A_2, \cdots, A_N)\),对于每个 \(k = 1, 2, \cdots, N\),求出 \(A\) 中有多少个长度为 \(k\) 的等差数列。

解题思路

注意到 \(N \le 80\),考虑动态规划。

定义 \(dp[i][x][y]\) 为长度为 \(i\) ,且最后两个数是 \(A_x\)\(A_y\) 的等差数列的个数,转移的时候,只需要在 \(A_x\) 之前遍历所有的 \(A_j\),使得 \(A_y - A_x = A_x - A_j\) 就可以了,这样就可以将 \(A_y\) 接在 \(A_j, A_x\) 的后边。那么 \(dp[i][x][y] = \sum\limits_{j = 1}^{x-1} dp[i-1][j][x]\),时间复杂度 \(O(N^4)\),是能过的。

下面考虑优化。为了计算 \(dp[i][x][y]\),我们实际上要找的是长度为 \(i-1\) ,结尾是 \(A_x\) ,且公差为 \(d = A_y-A_x\) 的等差数列的个数之和,这启发我们把相同的公差 \(d\) 存放在一起。定义 \(d[i][y]\) 表示长度为 \(i\),结尾是 \(A_y\) 的等差数列的哈希表,其中哈希表的 key 是公差 \(d\),value 是数量。这样我们实际只需要在 dp 的过程中维护 \(d[i][y]\) 即可,枚举 \(x\),算出 \(d = A_y-A_x\),在 \(d[i-1][x]\) 的哈希表中查找出公差 \(d\) 的数量即可。时间复杂度 \(O(N^3)\)

参考代码
#include <iostream>
#include <vector>
#include <unordered_map>

using namespace std;
using LL = long long;
const LL mod = 998244353;

int main()
{
    int n;
    cin >> n;
    vector<int> A(n);
    for(int i = 0; i < n; i++)
        cin >> A[i];
    if(n == 1)
    {
        cout << 1 << endl;
        return 0;
    }
    cout << n << ' ' << n * (n-1) / 2;
    vector<vector<unordered_map<int, LL>>> dp(n+1, vector<unordered_map<int, LL>>(n));
    for(int y = 0; y < n; y++)
        for(int x = 0; x < y; x++)
        {
            int d = A[y] - A[x];
            dp[2][y][d]++;
        }
    for(int i = 3; i <= n; i++)
    {
        LL sum = 0;
        for(int y = 2; y < n; y++)
            for(int x = 0; x < y; x++)
            {
                int d = A[y] - A[x];
                if(auto it = dp[i-1][x].find(d); it != dp[i-1][x].end())
                {
                    sum += it->second;
                    sum %= mod;
                    dp[i][y][d] += it->second;
                    dp[i][y][d] %= mod;
                }
            }
        cout << ' ' << sum;
    }
    return 0;
}

F - Perfect Matching on a Tree

题目大意

给你一棵 \(N(1 \le N \le 2 \times 10^5)\) 个节点的树。对于树上的两点 \(x\)\(y\),定义 \(w(x, y)\) 表示两点之间的距离。

求出树上的 \(\lfloor \frac{N}{2} \rfloor\) 对点 \((x_1, y_1), (x_2, y_2), \cdots, (x_{\lfloor \frac{N}{2} \rfloor}, y_{\lfloor \frac{N}{2} \rfloor})\),满足树上的点最多在其中出现一次,且最大化 \(\sum\limits_{i=1}^{\lfloor \frac{N}{2} \rfloor} w(x_i, y_i)\) 的值。

解题思路

考虑一条边能对答案产生的贡献,假设移除掉某条边 \((u, v)\) 之后,\(u\) 所在的子树有 \(c\) 个点,则 \(v\) 所在的子树有 \(N-c\) 个点,那么,这条边最多能对答案产生 \(\min(c, N-c)\) 次贡献。因此,如果每一条边的贡献都达到了 \(\min(c, N-c)\) ,那么答案就一定是最大的。

不失一般性的,不妨设 \(c \le N-c\),即点 \(u\) 所在的子树的点数量更少,那么我们需要让点 \(u\) 所在的子树的所有点都穿过边 \((u, v)\),也就是和 \(v\) 所在的子树进行匹配。这样可以让边 \((u, v)\) 的贡献最大。此外,这种方式还可以让 \(u\) 子树中所有的边的贡献最大化(因为子树中所有的点都必须穿过边 \((u, v)\) )。

这种方式实际上不需要考虑每个点究竟和哪个点匹配,只需要让点穿过边,让边的贡献达到最大即可。

这启发我们按照树的重心(如果不会树的重心请看 树的重心 - OI Wiki (oi-wiki.org))进行切分,假设树的重心是 \(G\)\(G\) 的所有子树有 \(T_1, T_2, \cdots, T_k\),同时设 \(E_i\) 表示 \(G\) 和子树 \(T_i\) 的根节点的连边。根据刚刚推导出来的性质,只需要考虑让每条边 \(E_i\) 的贡献最大即可,也就是让所有子树的点都和其他子树的点匹配(一个简短的证明:试想一下,如果 \(G\) 不是重心,那么存在一个子树的点的数量超过了 \(\frac{N}{2}\),这棵子树就会出现内部的匹配,这样就一定会有一些边的贡献没有最大化。)。

如何构造答案?一种方式是每次从最大和次大的子树中选择两个点,然后删掉这两个点继续选择。不过还有一种更简单的做法,依次遍历子树 \(T_1, T_2, \cdots, T_k\),按照访问的顺序把节点放到一个数组 \(v\) 中,然后让 \(v[i]\)\(v[i+\lfloor \frac{N}{2} \rfloor]\) 匹配,根据重心的性质,这种匹配方式可以保证两个点一定不是同一个子树。

如果 \(N\) 是偶数的话,最后剩下的点和 \(G\) 进行匹配即可(相当于把 \(G\) 加进数组 \(v\) 的末尾)。否则 \(N\) 是奇数,则 \(G\) 不参与匹配。

参考代码
#include <iostream>
#include <vector>

using namespace std;

const int maxn = 2E5 + 10;
const int maxm = maxn * 2;
int head[maxn], edge[maxm], ne[maxm], idx = 1;
void add(int u, int v)
{
    edge[idx] = v;
    ne[idx] = head[u];
    head[u] = idx++;
}

int n, c;
int dfs(int u, int fa)
{
    int son = 1;
    int M = 0;
    for (int i = head[u]; i; i = ne[i])
    {
        int v = edge[i];
        if (v == fa)
            continue;
        int sonv = dfs(v, u);
        son += sonv;
        M = max(M, sonv);
    }
    M = max(M, n - son);
    if (M <= n / 2)
        c = u;
    return son;
}

vector<int> dfn;
void dfs2(int u, int fa)
{
    dfn.emplace_back(u);
    for (int i = head[u]; i; i = ne[i])
    {
        int v = edge[i];
        if (v == fa)
            continue;
        dfs2(v, u);
    }
}

int main()
{
    cin >> n;
    for (int i = 1, u, v; i < n; i++)
    {
        cin >> u >> v;
        add(u, v);
        add(v, u);
    }
    dfs(1, 1);
    if (n % 2 == 0)
        dfn.emplace_back(c);
    for (int i = head[c]; i; i = ne[i])
    {
        int u = edge[i];
        dfs2(u, c);
    }
    for(int i = 0; i < n / 2; i++)
        cout << dfn[i] << ' ' << dfn[i + n / 2] << '\n';
    return 0;
}

G - Count Substring Query

题目大意

有一个字符串 \(S(1 \le |S| \le 5 \times 10^5)\)\(Q(1 \le Q \le 5 \times 10^5)\) 个询问,每次询问给你一个字符串 \(T_i\),其中 \(\sum\limits_{i=1}^Q|T_i| \le 5 \times 10^5\)。对于每一次询问,回答出 \(T_i\)\(S\) 中出现了几次。

解题思路

AC 自动机 板子题。

参考代码
#include <iostream>
#include <vector>
#include <queue>

using namespace std;

const int maxn = 5E5 + 10;
int cnt[maxn];
struct Trie
{
    int ne[maxn][26], fail[maxn], vis_cnt[maxn], node_cnt;
    vector<int> pattern_id[maxn];
    int next_pattern_id;
    vector<int> edge[maxn];

    void insert(string s)
    {
        int u = 0;
        for (auto ch : s)
        {
            int c = ch - 'a';
            if (!ne[u][c])
                ne[u][c] = ++node_cnt;
            u = ne[u][c];
        }
        pattern_id[u].emplace_back(next_pattern_id++);
    }

    void build()
    {
        queue<int> q;
        for (int c = 0; c < 26; c++)
            if (ne[0][c])
                q.push(ne[0][c]);
        while (!q.empty())
        {
            int u = q.front();
            q.pop();
            for (int c = 0; c < 26; c++)
            {
                int v = ne[u][c], f = fail[u];
                if (v)
                {
                    fail[v] = ne[f][c];
                    q.push(v);
                }
                else
                    ne[u][c] = ne[f][c];
            }
        }
    }

    void dfs(int u)
    {
        for(auto v : edge[u])
        {
            dfs(v);
            vis_cnt[u] += vis_cnt[v];
        }
        for(auto i : pattern_id[u])
            cnt[i] += vis_cnt[u];
    }

    void fail_tree()
    {
        for(int u = 1; u <= node_cnt; u++)
            edge[fail[u]].push_back(u);
        dfs(0);
    }

    void query(string s)
    {
        int now = 0;
        for (auto ch : s)
        {
            int c = ch - 'a';
            now = ne[now][c];
            vis_cnt[now]++;
        }
        fail_tree();
    }

} ac;

int main()
{
    string s;
    cin >> s;
    int q;
    cin >> q;
    string t;
    for(int i = 0; i < q; i++)
    {
        cin >> t;
        ac.insert(t);
    }
    ac.build();
    ac.query(s);
    for(int i = 0; i < q; i++)
        cout << cnt[i] << '\n';
    return 0;
}


最后更新: 2024-07-25
创建日期: 2024-07-25