题解 P4039 【[AHOI2014/JSOI2014]拼图】

龙之吻—水货

2019-03-25 14:26:49

Solution

# [AHOI2014/JSOI2014]拼图 ## 题目链接 ### [[AHOI2014/JSOI2014]拼图](https://www.luogu.org/problemnew/show/P4039) ## 解题报告 网上的题解都是根号分治,但是这里考虑一种$log$的做法。 首先感谢@[独秀平川](https://www.luogu.org/space/show?uid=53153)神仙告诉了我这个神奇的解法。 简单思考,可以发现,最优情况只有两种,一种是在一块拼图内部选取一个举行,一种是多个拼图联通起来,具体来说就是这个样子 : ![](https://cdn.luogu.com.cn/upload/pic/54978.png) 当然,可能会没有左区间或者右区间。 暂时不考虑第一种情况。 对于第二种情况,我们先预处理出每个格子,最多可以向上延伸多少(如果这个格子上的数字为 $1$ 那么是不可延伸的),只需要从上到下扫一遍即可。 之后,我们从下往上枚举所求矩形的下边界,寻找最优解。 考虑已知下边界如何求出最优解,显然,对于每一块拼图,情况都应该是这样子的: ![](https://cdn.luogu.com.cn/upload/pic/54982.png) 其中蓝色的表示从当前下边界最多可以往上延伸的部分。 尝试从上到下枚举高度,由于在离散化之后,高度最多有 $M$ 种,而 $n \times M \leq 10^5$,所以到现在时间复杂度都还是在可接受的范围内。 每枚举到一个高度,都需要求出每一块拼图从左边向内可以延伸多少,从右边向内可以延伸多少(特别地,如果可以延伸的长度等于该块拼图的长度,说明这一块拼图被打通了,可以作为中间的块)。 只要维护了这些东西,我们找出从左边延伸最多的拼图,从右边延伸最多的拼图,以及所有被打通了的拼图,将它们组合在一起就可以得到当前高度的最优解。 由于 从左边延伸最多的拼图 和 从右边延伸最多的拼图 可能是同一个拼图,所以我们可能会同时需要最大值和次大值。同时,当一个拼图被打通之后,需要将它对最大值和次大值的贡献删去。于是选择使用两个 $set$ 分别来存储没被打通的拼图。 至于如何在枚举高度之后快速求出所需要的值,只需要两个指针就可以了。但是为了顺便解决最优情况在一块拼图内部的情况,选择使用并查集。 对于每一块拼图,我们把每一列当作一个单独的点,将这个点可以向上延伸距离称为这个点的高度,由于是从上往下枚举高度,所以相邻的两点之间只可能是在不断地合并,每枚举到一个高度 $h$,我们就只需要将所有高度为 $h$ 的点和它相邻且高度为 $h$ 的点合并即可。如果当前点所在联通块的大小为这块拼图的宽度,说明当前拼图被打通了。 同时,当前点所在联通块的大小与当前枚举到的高度 $h$ 相乘,并用结果更新 $ans$ 就可以涵盖所有最优解在一块拼图的情况了。这个简单想一下就明白了吧? 附上Code。。。 ```cpp #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<string> #include<deque> #include<queue> #include<set> class Solution{ private : typedef std::pair<int, int> par; static const int maxn = 1e5 + 7; struct Block{ int len, **h; int *fa, *size; bool *open; std::string *str; inline int *operator [](int x) const { return this->h[x]; } int find(int k) { return fa[k] == k ? k : fa[k] = find(fa[k]); } int findSize(int k) { return size[find(k)]; } void merge(int x, int y) { if (!open[x] || !open[y]) { return; } x = find(x), y = find(y); if (x == y) { return; } fa[y] = x; size[x] += size[y]; } }; int t, s, n, ans; Block b[maxn]; std::deque<par> dq; std::priority_queue<std::pair<int, par> > pq; std::set<par, std::greater<par> > left, right; template<typename T> T next(T x) { ++x; return x; } template<typename T> T prev(T x) { --x; return x; } void make(const Block &b) { for (register int i = 1; i <= n; i++) { for (register int j = 1; j <= b.len; j++) { bool now = b.str[i][j - 1] - '0'; if (now) { b[i][j] = 0; } else { b[i][j] = b[i - 1][j] + 1; } //printf("%d ", b[i][j]); } //putchar('\n'); } //putchar('\n'); } int deal(int flor) { int res = 0; //printf("floor = %d : \n", flor); for (register int i = 1; i <= s; i++) { left.insert(std::make_pair(0, i)); right.insert(std::make_pair(0, i)); memset(b[i].open, 0, b[i].len + 2); b[i].fa[0] = 0, b[i].size[0] = 0, b[i].open[0] = 1; b[i].fa[b[i].len + 1] = b[i].len + 1, b[i].size[b[i].len + 1] = 0, b[i].open[b[i].len + 1] = 1; for (register int j = 1; j <= b[i].len; j++) { b[i].fa[j] = j, b[i].size[j] = 1; pq.push(std::make_pair(b[i][flor][j], std::make_pair(i, j))); } } int mid = 0; //fprintf(stderr, "%d\n", flor); while (!pq.empty()) { int height = pq.top().first, id = pq.top().second.first, pos = pq.top().second.second; pq.pop(); int leftLen = b[id].findSize(0), rightLen = b[id].findSize(b[id].len + 1); b[id].open[pos] = 1; b[id].merge(pos, pos - 1); b[id].merge(pos, pos + 1); ans = std::max(ans, b[id].findSize(pos) * height); //printf("%d %d : %d %d\n", id, pos, b[id].findSize(pos), height); left.erase(std::make_pair(leftLen, id)); right.erase(std::make_pair(rightLen, id)); if (b[id].findSize(0) == b[id].len) { //puts("233"); mid += b[id].findSize(0); } else { left.insert(std::make_pair(b[id].findSize(0), id)); right.insert(std::make_pair(b[id].findSize(b[id].len + 1), id)); } //printf("%d %d : %d\n", flor, height, mid); int sum = mid * height; res = std::max(res, sum); if (!left.empty()) { par l = *left.begin(); //printf(":: %d\n", l.first); res = std::max(res, sum + l.first * height); if (!right.empty()) { par r = *right.begin(); if (l.second == r.second) { if (right.size() > 1u) { r = *next(right.begin()); res = std::max(res, sum + (l.first + r.first) * height); } if (left.size() > 1u) { l = *next(left.begin()); res = std::max(res, sum + (l.first + r.first) * height); } } else { res = std::max(res, sum + (l.first + r.first) * height); } } } else { if (!right.empty()) { par r = *right.begin(); res = std::max(res, sum + r.first * height); } } //par leftFirst = *left.begin(); } //printf("%d %d\n", flor, res); return res; } public : Solution() { scanf("%d", &t); while (t--) { get(); solve(); } } void get() { scanf("%d %d", &s, &n); for (register int i = 1; i <= s; i++) { scanf("%d", &b[i].len); b[i].h = new int*[n + 1]; b[i].fa = new int[b[i].len + 2]; b[i].size = new int[b[i].len + 2]; b[i].open = new bool[b[i].len + 2]; b[i].str = new std::string[n + 1]; b[i].h[0] = new int[b[i].len + 1]; memset(b[i].h[0], 0, (b[i].len + 1) << 2); for (register int j = 1; j <= n; j++) { std::cin >> b[i].str[j]; b[i].h[j] = new int[b[i].len + 1]; memset(b[i].h[j], 0, (b[i].len + 1) << 2); } } } void solve() { //make(b[1]); //ans = getAns(b[1]); ans = 0; for (register int i = 1; i <= s; i++) { make(b[i]); //ans = std::max(ans, getAns(b[i])); } for (register int i = 1; i <= n; i++) { ans = std::max(ans, deal(i)); } printf("%d\n", ans); } }; Solution sol; int main() {} ```