2021,11,17 考试题解报告
T1 paint
题目描述
小 \(A\) 是一名画家。
现在有一张大小为 \(𝑛 \times m\) 的网格图,小 \(A\) 用 \(𝐾\) 种颜色在网格图上作画。
其中第 \(𝑖\) 种颜色编号为 \(𝑖\),初始时网格图中每个格子都没有颜色,编号
为 \(0\)。
已知每种颜色小 \(A\) 都会使用且只使用一次,但使用的顺序是未知的。
使用一种颜色时需要选定一个连续的子矩阵,将该子矩阵涂上这种颜
色。
后涂的颜色会覆盖之前的颜色。
现在给出小 \(A\) 画完后的图,问有多少种颜色可能是小 \(A\) 最先使用的。
数据范围
对于 \(30\%\) 的数据,\(1 ≤ 𝑛 \times 𝑚, 𝑘 ≤ 20\)
对于 \(60\%\) 的数据,\(1 ≤ 𝑛 \times 𝑚, 𝑘 ≤ 1000\)
对于另外 \(20\%\) 的数据,\(1 ≤ 𝑚, 𝑘 ≤ 10^5, 𝑛 = 1\)
对于 \(100\%\) 的数据,\(1 ≤ 𝑛 \times 𝑚, 𝑘 ≤ 10^5\)
solution
一个很显然的结论:不出现在最后网格图里面的颜色一定满足条件。
再来考虑出现在最后网格图里面的颜色。
当一个颜色的覆盖的范围中被另一个覆盖,那么这个颜色就不合法。
直接记录出每个颜色覆盖范围的四个端点,然后二阶差分搞一下就好了。
复杂度 \(O(n^2)\)
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 5e5 + 5;
const int INF = 0x3f3f3f3f3f;
int read(){
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9' ) {if (c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int N, M, K, tot, col[MAXN], sum[MAXN], ans;
struct Node{int lx, rx, ly, ry, fag, tag;}a[MAXN];
int id(int x, int y) {return (x - 1) * M + y;}
void INIT() {
for (int i = 0; i <= K; i++) a[i].lx = a[i].ly = INF;
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= M; j++) {
int x = read();
if(!a[x].tag) a[x].tag = 1, tot++;
a[x].lx = min(a[x].lx, i), a[x].ly = min(a[x].ly, j);
a[x].rx = max(a[x].rx, i), a[x].ry = max(a[x].ry, j);
col[id(i, j)] = x;
}
}
}
signed main() {
//freopen("paint.in", "r", stdin);
//freopen("paint.out", "w", stdout);
N = read(), M = read(), K = read();
INIT();
if(tot == 1) {if (K == 1) puts("1"); else cout<<K - 1;return 0;}
if(tot == 0) {puts("0");return 0;}
for (int i = 1; i <= K; i++) {
if(a[i].tag == 0) continue;
sum[id(a[i].lx, a[i].ly)]++, sum[id(a[i].rx + 1, a[i].ry + 1)]++;
sum[id(a[i].lx, a[i].ry + 1)]--, sum[id(a[i].rx + 1, a[i].ly)]--;
}
for (int i = 1; i <= N; i++)
for (int j = 1; j <= M; j++) sum[id(i, j)] = sum[id(i, j)] + sum[id(i - 1, j)] + sum[id(i, j - 1)] - sum[id(i - 1, j - 1)];
for(int i = 1;i <= N; i++)
for(int j = 1;j <= M; j++) if(sum[id(i, j)] > 1) a[col[id(i, j)]].fag = 1;
for(int i = 1;i <= K; i++) if(!a[i].fag) ans ++;
printf("%lld", ans);
return 0;
}
T2 seq
题目描述
有一个长度为 \(𝑛\) 的数列,数列中每个数都是 \([0, 𝑝 - 1]\) 之间的整数。
小 \(A\) 不知道数列中每个数的值,所以向小 \(B\) 做了 \(𝑚\) 次询问。
每次小 \(A\) 会向小B询问一个区间 \([𝑙, 𝑟]\) 中所有数的和对 \(𝑝\) 取模的结果。
问完所有问题后,小 \(A\) 发现小 \(B\) 的回答中似乎存在矛盾。
现在小 \(A\) 想找到最大的 \(𝑋\),满足小 \(B\) 的前 \(𝑋\) 次回答中不存在矛盾(𝑋有可
能等于 \(𝑚\) )。
数据范围
对于 \(30\%\) 的数据,\(1 ≤ 𝑛, 𝑚 ≤ 10, 𝑝 = 2\)
对于 \(60\%\) 的数据,\(1 ≤ 𝑛, 𝑚 ≤ 1000, 2 ≤ 𝑝 ≤ 1000\)
对于另外 \(30\%\) 的数据,\(1 ≤ 𝑛, 𝑚 ≤ 10^6, 𝑝 = 2\)
对于 \(100\%\) 的数据,\(1 ≤ 𝑛, 𝑚 ≤ 106, 2 ≤ 𝑝 ≤ 10^9\)
\(𝑝\) 不一定为质数,建议使用读入优化
solution
带权并查集。
用 \(b_i\) 表示数组 \(a_i\) 的前缀和。
发现一组询问 \(l, r, k\) 就表示 \((b_r - b_{l - 1}) \equiv k \pmod p\)
我们可以将用并查集维护该关系,并查集中每一条边维护该点和父亲差对 \(p\) 取模的结果。
如果点 \(l - 1\) 和点 \(r\) 不在一个并查集中,就将两者放入一个并查集中并设置边权。
如果点 \(l - 1\) 和点 \(r\) 在同一个并查集中,就判断两者之间的差是否合法。
时间复杂度 \(O(n\alpha(n))\)
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 1e6 + 5;
int read(){
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9' ) {if (c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int n, m, p, sum[MAXN], fa[MAXN];
struct Question{int l, r, k;}q[MAXN];
int find(int x) {
int F;
if(fa[x] != x) F = find(fa[x]), sum[x] += sum[fa[x]], fa[x] = F;
return fa[x];
}
signed main() {
//freopen("seq.in", "r", stdin);
//freopen("seq.out", "w", stdout);
n = read(), m = read(), p = read();
for (int i = 0; i <= n; i++) fa[i] = i;
for (int i = 1; i <= m; i++) q[i].l = read(), q[i].r = read(), q[i].k = read();
for (int i = 1; i <= m; i++) {
int fx = find(q[i].l - 1), fy = find(q[i].r);
if(fx != fy) fa[fx] = fy, sum[fx] = q[i].k + sum[q[i].r] - sum[q[i].l - 1];
else if((sum[q[i].l - 1] - sum[q[i].r] + p) % p != q[i].k) {cout<<i - 1;return 0;}
}
cout<<m;
return 0;
}
T3 bomb
题目描述
有 \(𝑛\) 个炸弹分布在一条数轴上。
现在需要玩家以一定的能量引爆某一个炸弹,而炸弹爆炸会引起连锁反应导致更多的炸弹爆炸。
炸弹爆炸时会携带能量 \(𝑋\),可以引爆和他距离 \(≤ 𝑋\) 的所有炸弹。
当一个炸弹被能量 \(𝑋\) 的爆炸引爆时,该炸弹会携带 \(⌊\frac{2X}{3} ⌋\)(下取整)的能量。
玩家可以设置第一个引爆的炸弹的能量,现在请问,如果想引爆所有炸弹,第一个引爆的炸弹的能量最少是多少呢?
数据范围
对于 \(30\%\) 的数据,\(2 \leq 𝑛 \leq 10\)
对于 \(60\%\) 的数据,\(2 \leq 𝑛 \leq 1000\)
对于 \(100\%\) 的数据,\(2 \leq 𝑛 \leq 10^6\)
保证炸弹坐标在 \([0, 500000000]\) 范围内。
solution
考虑 \(dp\)。
用 \(f_i\) 表示只考虑前 \(i\) 个炸弹,第 \(i\) 个炸弹引爆至少需要 \(f_i\) 的能量才能将前面所有的炸弹引爆。
之后用 \(g_i\) 表示只考虑第 \(i\) 个炸弹之后的所有,第 \(i\) 个炸弹引爆时至少需要 \(g_i\) 的能量才能将后面所有炸弹引爆。
然后最后输出 \(\min\{\max\{f_i, g_i\}\}\)。
转移是 \(n^2\) 的,可以获得 \(60\) 分的好成绩。
然后你发现决策是单调的,可以用单调队列进行优化。
复杂度 \(O(n)\)
为什么具有单调性 ?
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 1e6 + 5;
int read(){
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9' ) {if (c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int N, a[MAXN], head = 1, tail;
int f[MAXN], q[MAXN], g[MAXN];
int Calcf(int i, int j) {return max(abs(a[i] - a[j]), (f[j] * 3 + 1) >> 1);}
int Calcg(int i, int j) {return max(abs(a[i] - a[j]), (g[j] * 3 + 1) >> 1);}
signed main() {
N = read();
for (int i = 1; i <= N; i++) a[i] = read();
sort(a + 1, a + N + 1);
memset(f, 0x3f, sizeof f), memset(g, 0x3f, sizeof g);
f[0] = g[N] = 0;
head = 1, tail = 0;
q[++tail] = 1;
for (int i = 2; i <= N; i++) {
while(head < tail && Calcf(i, q[head]) >= Calcf(i, q[head + 1])) ++head;
f[i] = Calcf(i, q[head]);
while (head <= tail && Calcf(i, q[head]) >= Calcf(i, i)) --tail;
q[++tail] = i;
}
head = 1, tail = 0;
q[++tail] = N;
for (int i = N - 1; i >= 1; i--) {
while (head < tail && Calcg(i, q[head]) >= Calcg(i, q[head + 1])) ++head;
g[i] = Calcg(i, q[head]);
while (head <= tail && Calcg(i, q[tail]) >= Calcg(i, i)) --tail;
q[++tail] = i;
}
int Ans = 0x3f3f3f3f;
for (int i = 1; i<= N; i++) Ans = min(Ans, max(f[i], g[i]));
printf("%lld", Ans);
return 0;
}

浙公网安备 33010602011771号