【题解】8 月 10 日模拟赛题解

时间:2021-08-12
本文章向大家介绍【题解】8 月 10 日模拟赛题解,主要包括【题解】8 月 10 日模拟赛题解使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

前言

没啥好说的,估分 \(100 + 30 + 100 + 100 = 330\)​,实际 \(100 + 90 + 40 + 38 = 268\)​,显然实力不够导致挂分严重。\(A\)​ 是显然的数组模拟可持久化,\(B\)​ 直接显然贪心乱草,\(C\)​ 最离谱,想出了正解结果把 \(m\)​ 打成了 \(n\)​,\(100 - 60 = 40\)​。\(D\)​ 又是一个显然的 \(Tarjan\)​,结果半年没有写割点导致推出来式子然后代码炸了……只能说基础不扎实,每次基本都是这些 \(**\)​ 的地方 \(***\)​ 出错,我 \(**\)​ 的真 \(**\)​ 是个 \(**\)​ 的 \(**\)​,这么 \(**\)​ 的题目都 \(**\)​ 的写炸,真 \(***\)​ 的离谱。就 \(**\)​ 的四道 \(**\)​ 题目,我这种 \(**\)​ 的 \(***\)​ 都 \(**\)​ 能写成一坨 \(**\)​ 的 \(**\)​,我 \(***\)​ 有一万句 \(**\)​ 不知 \(***\)​​ 当不当讲……

这篇题解晚 \(2\) 天发出来的原因事忘记发布了。

\(T1\) 可持久化变量

题目大意

给出一个可持久化变量 \(a\)\(n\) 个操作​,每次可以:

  1. 给该变量的值增加 \(x\)
  2. 给该变量的值减少 \(x\)
  3. 给该变量赋值为 \(x\)
  4. 回溯之前的 \(x\) 个操作,使变量回到第 \(i - x\) 的操作前的状态

已知该变量修改过程中和结果的值均在 int 范围内,试输出每次操作后该变量的值。

\(1 \leq n \leq 10^5\)

解题思路

显然我们可以用一个数组来模拟这个变量的操作过程。设 \(s_i\)\(i\) 次操作 该可持久化变量的权值。对于操作 \(1\)\(s_i = s_{i - 1} + x\);对于操作 \(2\)\(s_i = s_{i - 1} - x\);对于操作 \(3\)\(s_i = x\);对于操作 \(4\),其本质相当于令该可持久化变量回到第 \(i - 1 - x\) 次操作 的状态,所以 \(s_i = s_{i - 1 - x}\)。时间复杂度 \(O(n)\)

参考代码

#include <cstdio>
using namespace std;
 
const int maxn = 1e5 + 5;
 
int n, x;
int val[maxn];
char opt[10];
 
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        scanf("%s", opt);
        if (opt[0] == 'A')
        {
            scanf("%d", &x);
            val[i] = val[i - 1] + x;
        }
        else if (opt[0] == 'S' && opt[1] == 'U')
        {
            scanf("%d", &x);
            val[i] = val[i - 1] - x;
        }
        else if (opt[0] == 'S' && opt[1] == 'E')
        {
            scanf("%d", &x);
            val[i] = x;
        }
        else
        {
            scanf("%d", &x);
            val[i] = val[i - 1 - x];
        }
    }
    for (int i = 1; i <= n; i++)
        printf("%d ", val[i]);
    puts("");
    return 0;
}

\(T2\) \(\texttt{Recording the Moolypics}\)

题目大意

题目链接

给出 \(n\) 条线段 \([s_i, t_i]\)​。假设我们有两个初始值为 \(0\) 的终点变量。已知对于任意一条线段,如果这条线段的起点大于等于当前选择的终点变量,那么您可以将这个终点变量赋值为这条线段的起点并选择这条线段。试求最多可以选择的线段数量。

\(1 \leq n \leq 150, 0 \leq s_i \leq t_i \leq 10^9\)

解题思路

看到题目需要求最值优先考虑贪心和 \(dp\)。发扬我们的优良乱搞传统,首先考虑贪心。假如只有一个终点变量可以维护,那么优先选择右端点较小的线段显然最优。现在题目中给出了两条线段,考虑多出的线段对于答案的影响。假设两个终点变量的值分别为 \(x, y\),那么现在线段已经依据右端点为第一关键字、左端点为第二关键字从小到大排序。如果 \(x. y > s_i\),说明当前线段无法被选择。只有一个终点变量可以被更新时,否则,我们把第 \(i\)​​ 条线段贡献给值较大的终点变量。随便感性理解一下,贡献给值较大的终点变量可以使终点变量与 \(s_i\) 之间的差距尽量小,如果贡献给另外一个终点变量可能会横跨最优方案中的两条线段。总而言之,这种贪心题就是 大胆猜想,不用证明

据说还有记忆化搜索写法,但是我不会也懒得想,所以没有写出来这种做法。

话说晚饭吃的黑椒猪排套餐挺好吃的,就是有点贵,我攒了两顿饭的钱吃一顿饭来着

我不会说这是因为我要买咖啡所以原本丰腴的荷包君渐渐空虚

参考代码

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
 
const int maxn = 200;
 
struct node
{
    int s, t;
    bool operator < (const node& rhs) const
    {
        if (t != rhs.t)
            return t < rhs.t;
        return s < rhs.s;
    }
} a[maxn];
 
int n, ans;
 
int main()
{
    int l = 0, r = 0;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d%d", &a[i].s, &a[i].t);
    sort(a + 1, a + n + 1);
    for (int i = 1; i <= n; i++)
    {
        if (l < r)
            swap(l, r);
        if (a[i].s >= l)
        {
            ans++;
            l = a[i].t;
        }
        else if (a[i].s >= r)
        {
            ans++;
            r = a[i].t;
        }
    }
    printf("%d\n", ans);
    return 0;
}

\(T3\ \texttt{Wormhole Sort}\)

题目大意

题目链接

给出一个包含 \(n\)\(m\) 边的无向图,图中的边带权。您需要选出若干条边使得 \(\forall 1 \leq i \leq n\),原图在仅保留选出的若干条边时结点 \(p_i\) 和结点 \(i\)​ 是连通的。试求选出的边中最小边权的最大值。

\(1 \leq n, m \leq 10^5, \forall 1 \leq i \leq m, 1 \leq w_i \leq 10^9\)

解题思路

看到关键字 最大的最小值,结合数据范围可知这道题大概率使用 二分。考虑二分最大的最小边权,每次将边权大于等于最小边权的无向边的两个端点所在的 并查集 合并。最后如果存在与结点 \(p_i\) 不连通的结点 \(i\),说明当前的最小边权不合法,反之合法。

时间复杂度 \(O(nlogn)\)

话说赛时估错复杂度结果想出一个最大生成树加树剖来着

参考代码

#include <cstdio>
#include <algorithm>
using namespace std;
 
const int maxn = 1e5 + 5;
const int maxm = 1e5 + 5;
 
struct node
{
    int u, v, w;
    bool operator < (const node& rhs) const
    {
        return w < rhs.w;
    }
} edge[maxm];
 
int n, m;
int p[maxn], fa[maxn];
 
void init()
{
    for (int i = 0; i < maxn; i++)
        fa[i] = i;
}
 
int get(int x)
{
    if (fa[x] == x)
        return x;
    return fa[x] = get(fa[x]);
}
 
void merge(int x, int y)
{
    x = get(x);
    y = get(y);
    if (x != y)
        fa[y] = x;
}
 
bool check(int x)
{
    init();
    for (int i = x; i <= m; i++)
        merge(edge[i].u, edge[i].v);
    for (int i = 1; i <= n; i++)
    {
        if (get(i) != get(p[i]))
            return false;
    }
    return true;
}
 
int main()
{
    bool flag = false;
    init();
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &p[i]);
        if (p[i] != i)
            flag = true;
    }
    for (int i = 1; i <= m; i++)
    {
        scanf("%d%d%d", &edge[i].u, &edge[i].v, &edge[i].w);
        merge(edge[i].u, edge[i].v);
    }
    sort(edge + 1, edge + m + 1);
    if (!flag || !check(1))
    {
        puts("-1");
        return 0;
    }
    int l = 1, r = m;
    while (l < r)
    {
        int mid = (l + r + 1) / 2;
        if (check(mid))
            l = mid;
        else
            r = mid - 1;
    }
    printf("%d\n", edge[l].w);
    return 0;
}

\(T4\ \texttt{BLO}\)

题目大意

题目链接

给出一个包含 \(n\)\(m\) 边的无向图,试求从图中删除任意一个结点以后,在原图中连通、在新图中不连通的有序点对数量。注意,这里的有序点对 \((a, b)\) 指满足 \(a < b\) 或者 \(a > b\) 的点对,并且需要考虑被删除的点与其他点不连通的情况。

\(1 \leq n \leq 10^5, m \leq 5 \times 10^5\)

解题思路

看到题目需要维护连通性,结合数据范围分析,不难想到用 \(Tarjan\) 维护。从图中删去任意一个结点会导致原本连通的点对不连通,等价于图中的极大连通分量个数增加,从而根据性质联想到 割点。显然对于非割点结点,删除它只会导致它本身与剩下的 \(n - 1\) 个结点不连通,因此满足要求的有序点对共有 \(2 \times (n - 1)\) 组。

对于割点,删除它以后图中会形成若干个新的连通分量。这些连通分量内部的结点互相连通,连通分量之间不连通,因此满足要求的有序点对数量为若干连通分量两两之间的结点个数乘积之和加上原本的 \(2 \times (n - 1)\) 组点对。考虑如何维护产生的若干个连通分量的大小。我们发现将原图转换称搜索树思考更加方便,对于割点 \(u\) 而言,删除它以后会产生的连通分量为搜索树上它的若干棵子树以及搜索树中结点 \(u\)​ 的子树以外的部分。在 Tarjan 函数中顺便维护每一棵子树大小,最后统计答案即可。

参考代码

#include <cstdio>
#include <vector>
#include <stack>
using namespace std;
  
const int maxn = 1e5 + 5;
const int maxm = 1e6 + 5;
  
struct node
{
    int to, nxt;
} edge[maxm];
  
int n, m;
int cnt, tot;
int dfn[maxn], low[maxn];
int head[maxn], size[maxn];
bool in_stack[maxn], cut[maxn];
vector<int> son[maxn];
stack<int> s;
  
void add_edge(int u, int v)
{
    cnt++;
    edge[cnt].to = v;
    edge[cnt].nxt = head[u];
    head[u] = cnt;
}
  
void tarjan(int u, int fa)
{
    int res = 0;
    tot++;
    size[u] = 1;
    dfn[u] = low[u] = tot;
    in_stack[u] = true;
    for (int i = head[u]; i; i = edge[i].nxt)
    {
        int v = edge[i].to;
        if (!dfn[v])
        {
            tarjan(v, u);
            size[u] += size[v];
            low[u] = min(low[u], low[v]);
            if (low[v] >= dfn[u])
            {
                cut[u] = true;
                son[u].push_back(size[v]);
                res += size[v];
            }
        }
        else if (in_stack[v])
            low[u] = min(low[u], dfn[v]);
    }
    if (cut[u] && res < n)
        son[u].push_back(n - res - 1);
}
  
int main()
{
//  freopen("P3469_1.in", "r", stdin);
    int u, v, len;
    long long ans;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++)
    {
        scanf("%d%d", &u, &v);
        add_edge(u, v);
        add_edge(v, u);
    }
    for (int i = 1; i <= n; i++)
        if (!dfn[i])
            tarjan(i, -1);
    for (int i = 1; i <= n; i++)
    {
        ans = 2 * (n - 1);
        if (son[i].size() >= 2)
        {
            len = son[i].size();
            for (int j = 0; j < len; j++)
                for (int k = j + 1; k < len; k++)
                    ans += (2LL * son[i][j] * son[i][k]);
        }
        printf("%lld\n", ans);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/lingspace/p/2021-8-10.html