跳转至

CSP模拟赛day1

T1 纸张(paper)

知识点:模拟

维护两个二维数组 $a,b$。其中 $a_{i,j}$ 记录 $(i,j)$ 的颜色,$b_{i,j}$ 记录 $(i,j)$ 是否存在遮蔽胶带。

  • 操作一:对四个格子进行涂色操作,前提是所涂格子并未被遮蔽胶带覆盖。
  • 操作二:对四个格子进行覆盖操作,将对应位置的 $b_{i,j}$ 置为 $1$ 表示存在遮蔽胶带。

操作完成后,遍历整个矩阵输出所有 $a_{i,j}$ 的值即可。(若没有颜色就是 $0$)

时间复杂度:$O(q+nm)$


T2 寒冬暖炉(stove)

知识点:贪心

因为客人在的时刻是零零散散的,所以就会存在一些无客人的时间间隔,而要想使炉子运行总时间最短,那么我们应尽可能地在合适的时刻及时关闭炉子,避免在无客人时造成炉子浪费,即尽可能做到只有有客人在时炉子才开着。

而在下一次来客人时再打开炉子是需要消耗火柴的,而火柴数量又有限,所以有时我们可能需要保持炉子运行(即使此时无客人)。

当第一位客人前来时我们必须消耗一根火柴打开炉子,但如果一直保持运行到最后一名客人走显然是不理智的。我们需要尽可能的避免较长时间无客人间隔运行炉子,此时我们就可在当前客人走时关闭炉子,在下一名客人来时再开炉子。即每多一根火柴,我们就可尽可能的避免一个无客间隔浪费。

综上分析,我们可以分别计算各无客间隔时长(共 $n−1$ 个无客间隔),然后优先依靠炉子关开避免较大无客间隔期间的浪费。

时间复杂度:$O(n\log{n})$

参考代码
int ans = a[n] - a[1] + 1; // 全程时长
for (int i = 1; i < n; i++)
{
    b[i] = a[i + 1] - a[i] - 1; // 分别计算各无客间隔时长
}
sort(b + 1, b + n, greater<int>()); // 对各无客间隔时长排序
for (int i = 1; i <= k - 1; i++)  // 去掉 k - 1 个较大的间隔
{
    ans -= b[i];
}
cout << ans;

T3 方格染色(color)

知识点:二分,贡献法。

子任务 $1,2$

定义 $\text{color}[i][j]$ 表示 $(i,j)$ 的颜色。按照题目描述的染色过程去实现即可。

时间复杂度:$O(n^2)$。


首先格子有 $n^2$ 个,但不同的数值最多只有 $2n$ 个。因此需要考虑每个数值如果能作为 $\max$ 对答案的贡献是多少。

我们注意到,对于任意格子 $(i,j)$,其中 $(i,j\geq 2)$。由于染色过程具有 “最大值传递“ 的性质,有

\[ \text{color}(i,j)=\max(a_1,\ldots,a_i,b_1,\ldots,b_j) \]

如果先将两个序列分别求前缀 $\max$ 的更新,使得

\[ a_i=\max(a_1,\ldots,a_i),\quad b_j=\max(b_1,\ldots,b_j) \]

那么网格中每个格子的颜色就可以直接写成

\[ \text{color}(i,j)=\max(a_i,b_j) \]

考虑枚举序列 $a$,即遍历行:$2\sim n$。那么就要思考:每一行的 $a_i$ 会对自己所在列产生什么贡献。

显然 $b$ 序列中 小于或等于 $a_i$ 的都可以视作是 $a_i$ 要产生贡献。而由于前缀 $\max$ 数组单调不下降。因此可以使用 upper_bound() 找到刚好大于 $a_i$ 的位置记作 $j$,则 $a_i$ 在 $2\sim j-1$ 列都是 $a_i$ 当最大值。此时答案直接更新 cnt[a[i]] += j - 2 即可。

这样考虑完以后在考虑序列 $b$ 的每个元素对答案的贡献,同理 $a$ 序列中 小于 $b_i$ 的都可以视作是 $b_i$ 要产生贡献。这部分使用 lower_bound() 求出即可。

第一行和第一列单独处理,最终时间复杂度为 $O(n\log{n})$。

参考代码
map<int, int> cnt;
cin >> n;
for (int i = 1; i <= n; i++)
    cin >> a[i], cnt[a[i]]++;
for (int i = 1; i <= n; i++)
    cin >> b[i], cnt[b[i]]++;
cnt[b[1]]--;
for (int i = 2; i <= n; i++)
{
    a[i] = max(a[i - 1], a[i]); 
    b[i] = max(b[i - 1], b[i]);
}
for (int i = 2; i <= n; i++)
{
    int j = upper_bound(b + 2, b + n + 1, a[i]) - b;
    cnt[a[i]] += j - 2;
}
for (int i = 2; i <= n; i++)
{
    int j = lower_bound(a + 2, a + n + 1, b[i]) - a;
    cnt[b[i]] += j - 2;
}
// 最后遍历 map 找一下答案即可

T4 画展(art)

知识点:贪心,排序。

关于画框

  • 画框越大越好(可容纳更多画)。
  • 实际安放时,可将画框按尺寸从大到小使用。

关于画

  • 一旦确定“要用哪些画”(一个集合),它们的排列顺序就被唯一确定
    • 从左到右按价值升序排列;
    • 若价值相同,则按尺寸从小到大排(反过来会出问题)。

子任务1

  • 决定要用哪些画共 $2^n$ 种。
  • 画与画框的顺序就确定了(按前述规则)。
  • 逐一判断每幅画能否装入对应的画框

子任务2

  • 先把所有画和画框按“将被使用的顺序”各自排序好。
  • 若“第 $i$ 幅画”装进了“第 $j$ 个画框”并被展出,那么其右侧只会出现编号更大的画编号更大的画框
  • 因而可做二维 dp

  • 定义:$\text{dp}[i][j]$:使用前 $i$ 幅画前 $j$ 个画框时,可展出画的最大数量

$$ temp=\max(\text{dp}[i-1][j],\ \text{dp}[i][j-1]). $$

  • 转移:
    • 画 $i$ 装不进框 $j$:$\text{dp}[i][j]=temp$。
    • 装得进:$\text{dp}[i][j]=\max\big(temp,\ \text{dp}[i-1][j-1]+1\big)$。
  • 复杂度 $O(nm)$。

子任务3

  • 画框按尺寸从大到小画按价值从高到低处理。
  • 依次尝试:若当前画能装进当前最大的可用画框,就装入并计数;否则跳过不合适的画或继续考虑更小的框。
  • 复杂度 $O(n\log n + m\log m)$,可满分

贪心解释

如果先尝试小价值的画匹配小尺寸的画框,会出现错误。例如:

  • 画 $1$:大小 $100$,价值 $50$。
  • 画 $2$:大小 $1$,价值 $99$。
  • 画 $3$:大小 $2$,价值 $100$。
  • 画框 $1$:尺寸 $2$。
  • 画框 $2$:尺寸 $100$。

如果按照从左往右的方式匹配(画找画框),会出现:

  • 第一幅画直接装进画框 $2$ 从而导致后续无法展出画 $2$ 和 $3$。

而如果从右往左考虑,则首先画 $3$ 装进框 $2$,画 $2$ 装进框 $1$,得到最大展出数:$2$。


为啥“从右往左、先大后小”能避免这个坑?

把顺序反过来(右往左)后,你面对的是一个随步缩小的“价值上界”问题

  • 初始价值上界为 $+\infty$;
  • 处理当前(较大的)画框时,从“尺寸能放、且价值 $\le$ 上界”的集合里挑价值最大的
  • 选完把上界变为这个价值;下一步(更小的画框)只能选“不超过这个上界”的里边的最大值——这恰好保证了左到右的非降,同时尽量把价值“顶到上界”,不浪费位置。

一句话:

  • 左→右很难“为未来留出空间”,因为你不知道后面上界会是多少;
  • 右→左则把“未来”的限制变成了一个当前可见的上界,于是“取不超过上界的最大值”就成了正确贪心。
参考代码
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 1e5 + 5;
int n, m, c[N];
struct picture 
{
    int s, v;
} a[N];
bool cmp(picture a, picture b)
{
    if (a.v != b.v) return a.v > b.v;
    return a.s > b.s;
}
int main()
{
    freopen("art.in", "r", stdin);
    freopen("art.out", "w", stdout);
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i].s >> a[i].v;
    sort(a + 1, a + n + 1, cmp);
    for (int i = 1; i <= m; i++) cin >> c[i];
    sort(c + 1, c + m + 1, greater<int>());
    int ans = 0;
    for (int i = 1, j = 1; i <= n && j <= m; i++)
    {
        if (c[j] >= a[i].s)
        {
            ans++;
            j++;
        }
    }
    cout << ans;
    return 0;
}