您的位置:首页 > 其它

2017多校联合第二场 1008题 hdu 6052 To my boyfriend 计数 单调队列

2017-07-31 18:02 477 查看
题目链接

题意:

给定一个矩阵 (1 <= n, m <= 100),每个格子都有一种颜色 (0 < color < n * m),对于它的每一个子矩阵,其 value 值等于该矩阵中出现过的颜色的个数,求 value 值的期望。

推荐:

http://blog.csdn.net/calabash_boy/article/details/76272704

原Po真的每一篇都写得很详细认真哇

思路:

显然,题目要求的就是所有子矩形的 value 值的总和,题意与第一场那道 colorful tree 几乎是一模一样(除了一个设定在树上,一个设定在矩阵中)。

计数问题,还是考虑部分对整体的贡献。那么这道题里将什么作为
部分 来考虑呢?

考虑一个方格对一个矩阵的贡献。假如该矩阵中有多个方格与该方格颜色相同,那么只算最上方最左边的那一个方格的贡献。

也就是说,对于某个矩阵 M 中的某颜色 c 而言,其 只会被算在 该矩阵中 出现在 最上方最左边的 颜色为 c 的 格子 p 中,

只会在算格子 p 时,算上 c 对 M 的贡献;而对于 M 中其他颜色为 c 的格子,则不再计算。

所以,现在的目标很明确了,对于给定的格子 p,我们要去算它对多少矩阵有贡献。

显然,下边界是可以一直延伸到最下方的,因为包含下方的格子不会影响 p 的最小性。

又显然,右边界与左边界是依据上边界而确定的。

对于右边界,在同一行内是可以一直向右延伸的,因为同理,包含同一行右边的格子不会影响 p 的最小性。

但是往上一行去找,其右边最远就要受到限制了;而越往上,右边最远延伸的距离是单调减的。

这里可以用单调队列去处理。维护一个单调增的队列,队头即为在该行可以向右延伸的最远距离。

至于左边界。在同一行内就要受限制,往上和右边界同理,也是用单调队列用一样的方法处理。

实现细节相关:

一开始 以行和列 循环,最开始开了一个 vector<int> color[maxn][maxn * maxn] 记录 每一行 每一种颜色 所在的列号,为了方便 每个格子的颜色 向上找时用 lower_bound 直接确定该行该颜色的左右边界,然而显然 MLE了...

于是就改成了 以颜色 为外层循环,做每一种颜色的时候再去记录 vector<int> line[maxn] 即该颜色在 每一行 的 列号,这样就过了。

其实本来写之前也就应该考虑到这一点的,原来那样记浪费的空间也实在是太多了,一种颜色一种颜色的去清,空间的利用效率就高很多了。

AC代码如下:

#include <bits/stdc++.h>
#define maxn 110
using namespace std;
struct span { int l, r; }span[maxn];
int st1[maxn], st2[maxn], color[maxn][maxn];
struct pos {
int x, y;
pos(int a = 0, int b = 0) : x(a), y(b) {}
};
vector<pos> col[maxn * maxn];
vector<int> line[maxn];
typedef long long LL;
void work() {
int n, m;
scanf("%d%d", &n, &m);
for (int i = 0; i < m * n; ++i) col[i].clear();
memset(span, 0, sizeof(span));
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
int temp;
scanf("%d", &temp);
color[i][j] = temp;
col[temp].push_back(pos(i, j));
}
}
LL ans = 0;
for (int c = 0; c < m * n; ++c) {
if (col[c].empty()) continue;
for (int i = 0; i < n; ++i) line[i].clear();
for (auto ele : col[c]) {
line[ele.x].push_back(ele.y);
}
for (auto ele : col[c]) {
int tot1 = 0, tot2 = 0, i = ele.x, j = ele.y;

span[i].r = m - j;

auto it = lower_bound(line[i].begin(), line[i].end(), j);
if (it == line[i].begin()) span[i].l = j + 1;
else span[i].l = j - *(it - 1);
st2[tot2++] = span[i].l;

for (int k = i - 1; k >= 0; --k) {
auto it = lower_bound(line[k].begin(), line[k].end(), j);
int diff1;
if (it == line[k].end()) diff1 = m - j;
else diff1 = *it - j;
if (diff1 == 0) {
span[k].r = 0;
break;
}
while (tot1 > 0 && diff1 < st1[tot1 - 1]) --tot1;
st1[tot1++] = diff1;
span[k].r = st1[0];

int diff2;
if (it == line[k].begin()) diff2 = j + 1;
else diff2 = j - *(it - 1);
if (diff2 == 0) {
span[k].l = 0;
break;
}
while (tot2 > 0 && diff2 < st2[tot2 - 1]) --tot2;
st2[tot2++] = diff2;
span[k].l = st2[0];
}
// printf("i j : %d %d\n", i, j);
for (int k = i; k >= 0; --k) {
// printf("%d %d %d\n", k, span[k].l, span[k].r);

if (span[k].l == 0 || span[k].r == 0) break;
ans += (LL)span[k].l * span[k].r * (n - i);
}
}
}
int num = (n * (n + 1) >> 1) * (m * (m + 1) >> 1);
// printf("%lld %d\n", ans, num);
printf("%.9f\n", (double)ans / num);
}
int main() {
int T;
scanf("%d", &T);
while (T--) work();
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: