2021“MINIEYE杯”中国大学生算法设计超级联赛(1) 杭电多校1 题解

sdxjzsq

2021-07-24 02:36:24

Personal

## 目录 | 题号 | 题目 | 知识点 | 完成度 | | ---- | ------------------------------------------------------------ | ---------------- | ------------ | | 1008 | HDU6957 [Maximal submatrix](https://acm.hdu.edu.cn/showproblem.php?pid=6957) | 悬线法/单调栈 | 单调栈已完成 | | 1010 | HDU6959 [zoto](https://acm.hdu.edu.cn/showproblem.php?pid=6959) | 莫队+分块/树状数组 | 莫队+树状数组做法已完成 | | 1009 | HDU6958 [KD-Graph](https://acm.hdu.edu.cn/showproblem.php?pid=6958) | Kruskal | - | | 1006 | HDU6955 [Xor sum](https://acm.hdu.edu.cn/showproblem.php?pid=6955) | 01trie | - | | 1007 | HDU6956 [Pass!](https://acm.hdu.edu.cn/showproblem.php?pid=6956) | - | - | | 1011 | HDU6960 [Necklace of Beads](https://acm.hdu.edu.cn/showproblem.php?pid=6960) | - | - | | 1004 | - | - | - | | 1003 | - | - | - | | 1002 | - | - | - | ## 1008 HDU6957 [Maximal submatrix](https://acm.hdu.edu.cn/showproblem.php?pid=6957) #### 思路1:单调栈法 首先预处理出 自该位置向上 满足性质的数 的个数,记为 $h[i][j]$ 对于每行 ( $h[i]$ ) 维护一个单调(递增)栈,所有元素进栈和出栈一次,每个元素出栈时更新最大的矩形面积。 为了能够更新最大的矩形面积,我们需要在栈内记录两个数据——元素的高度 $h[i][j]$​ 和它到栈中上一个元素的宽度差。 元素出栈时,因为高度逐渐递减,故记 $width$ 为当前已经弹出的元素的宽度和 #### code1(单调栈法) ```cpp #include <algorithm> #include <cstdio> using namespace std; const int maxn = 2e3 + 7; int t, n, m, v[maxn][maxn], h[maxn][maxn], ans, top, s[maxn], w[maxn]; void solve(int x) { s[top = 0] = 0; h[x][m + 1] = 0; for (int i = 1; i <= m + 1; ++i) { int width = 0; while (h[x][i] < s[top]) { width += w[top]; ans = max(ans, s[top--] * width); } s[++top] = h[x][i]; w[top] = width + 1; } } int main() { for (scanf("%d", &t); t--;) { ans = 0; scanf("%d%d", &n, &m); for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) { scanf("%d", &v[i][j]); if (v[i][j] >= v[i - 1][j]) h[i][j] = h[i - 1][j] + 1; else h[i][j] = 1; } for (int i = 1; i <= n; ++i) solve(i); printf("%d\n", ans); } return 0; } ``` #### 思路2(悬线法) 待补 #### code2(悬线法) ```cpp ``` ## 1010 HDU6959 [zoto](https://acm.hdu.edu.cn/showproblem.php?pid=6959) #### 题意 已知平面上有 $n$ 个点,分别为 $(i,fx[i])$,现在有 $m$ 个询问,每个询问为一个矩形,统计矩形内的点有多少个不同的 $y$ 值。 #### 思路1(莫队+树状数组) 首先,看到数据量很容易想到莫队的根号算法,但是二维莫队并不好 $O(1)$ 同时维护 $x$ 值和 $y$ 值的变化,因此我们先不考虑 $y$ 值,只考虑 $x$ 值,发现 $x$ 值可以使用一个典型的莫队算法维护,剩下便是 $y$ 值如何维护了。 每个询问其实就是询问 $[y1,y2]$ 间有多少个不同的 $y$ 上有点,我们考虑开 $10^5$ 个桶来记录某个 $y$ 上有多少个点,然后当桶中的值由 $1$ 变 $0$ 或由 $0$ 变 $1$ 的时候用树状数组/线段树来维护某个 $y$ 是否有值,可以发现在点数较多的情况下对树状数组的修改其实较少,这样每次区间移动便可以做到近似 $O(1)$ 的修改。 至此,本题便完成了,最差复杂度 $O(n\sqrt n \log n)$。 #### code1(莫队+树状数组) ``` cpp #include <algorithm> #include <cmath> #include <cstdio> #include <cstring> using namespace std; const int maxn = 1e5 + 7; int t, n, m, f[maxn], num[maxn], bucket[maxn], ans[maxn], B, belong[maxn]; struct Node { int id, l, r, ql, qr; bool operator<(const Node &rhs) const { if (belong[l] != belong[rhs.l]) return belong[l] < belong[rhs.l]; return r < rhs.r; } } q[maxn]; void build() { B = n / sqrt(m); for (int i = 1; i <= n; i++) belong[i] = (i - 1) / B + 1; } int query(int x) { int ans = 0; for (; x > 0; x -= x & -x) ans += num[x]; return ans; } void add(int x) { ++bucket[x]; if (bucket[x] == 1) for (; x < maxn; x += x & -x) ++num[x]; } void del(int x) { --bucket[x]; if (!bucket[x]) for (; x < maxn; x += x & -x) --num[x]; } int main() { for (scanf("%d", &t); t--;) { scanf("%d%d", &n, &m); memset(bucket, 0, sizeof(bucket)); memset(num, 0, sizeof(num)); build(); for (int i = 1; i <= n; ++i) scanf("%d", &f[i]), ++f[i]; for (int i = 0, x0, y0, x1, y1; i < m; ++i) { scanf("%d%d%d%d", &x0, &y0, &x1, &y1); q[i] = {i, x0, x1, y0, y1}; } sort(q, q + m); for (int i = 0, l = 1, r = 0; i < m; i++) { while (r < q[i].r) add(f[++r]); while (r > q[i].r) del(f[r--]); while (l < q[i].l) del(f[l++]); while (l > q[i].l) add(f[--l]); ans[q[i].id] = query(q[i].qr + 1) - query(q[i].ql); } for (int i = 0; i < m; ++i) printf("%d\n", ans[i]); } return 0; } ``` #### 思路2(莫队+分块) #### code1(莫队+分块) ``` cpp ```