Loading

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\) 的能量才能将前面所有的炸弹引爆。

\[f_i = \min_{j < i} \{\max\{3f_j / 2, a_i - a_j\}\} \]

之后用 \(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;
}
 
posted @ 2021-11-17 19:52  Dita  阅读(202)  评论(0)    收藏  举报