折半枚举(双向搜索)
POJ 2785:4 Values whose Sum is 0
从四个数列中选择的话总共有n4种情况,所以全都判断一遍是不可行的。
所以对半分成AB和CD考虑。从2个数列中选择的话只有n2种组合,就可以快速解决了。从2个数列中选择的话只有n2种组合,所以可以进行枚举。先从A,B中取出a, b后,为了使总和为0则需要从C,D中取出c+d=−(a+b)。因此先将C,D中取数字的n2种方法全部枚举出来,将这些和排好序,这样就可以运用二分搜索了。这个算法的复杂度是O(n2logn)
有时候,问题的规模比较大,无法枚举所有元素的组合,但能够枚举一半元素的组合。此时,将问题拆成两半后分别枚举,再合并他们的结果这一方法往往非常有效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int MAXN = 5010;
int n; int A[MAXN], B[MAXN], C[MAXN], D[MAXN]; int CD[MAXN * MAXN];
void solve() { for(int i = 0; i < n; i++) { for(int j = 0; j < n; j++) { CD[i * n + j] = C[i] + D[j]; } } sort(CD, CD + n * n); long long res = 0; for(int i = 0; i < n; i++) { for(int j = 0; j < n; j++) { int cd = -(A[i] + B[j]); res += upper_bound(CD, CD + n * n, cd) - lower_bound(CD, CD + n * n, cd); } } printf("%lld\n", res); }
int main() { scanf("%d", &n); for(int i = 0; i < n; i++) { scanf("%d%d%d%d", &A[i], &B[i], &C[i], &D[i]); } solve(); }
|
坐标离散化
本来这道题可以用深度优先搜索的方法来求解被分割出的区域个数。但这个问题中,w和h最大为1000000,所以没办法创建w×h 的数组,因此,利用坐标离散化来解决。
如上图,将前后没有变化的行列消除后并不会影响区域的个数。
数组里只需要存储有直线的行列以及其前后的行列就足够了,这样的话大小最多6n×6n就足够了。
因此就可以创建出数组并利用广度搜索(深度搜索可能会爆栈)就能求出区域的个数了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| #include <bits/stdc++.h> using namespace std;
const int MAXN = 100; int dx[4] = {-1, 0, 1, 0}; int dy[4] = {0, -1, 0, 1};
int W, H, N; int X1[MAXN], X2[MAXN], Y1[MAXN], Y2[MAXN];
bool fld[MAXN * 6][MAXN * 6];
int compress(int *x1, int *x2, int w) { vector<int> xs; for(int i = 0; i < N; i++) { for(int d = -1; d <= 1; d++) { int tx1 = x1[i] + d, tx2 = x2[i] + d; if(1 <= tx1 && tx1 <= W) { xs.push_back(tx1); } if(1 <= tx2 && tx2 <= W) { xs.push_back(tx2); } } } sort(xs.begin(), xs.end()); xs.erase(unique(xs.begin(), xs.end()), xs.end()); for(int i = 0; i < N; i++) { x1[i] = find(xs.begin(), xs.end(), x1[i]) - xs.begin(); x2[i] = find(xs.begin(), xs.end(), x2[i]) - xs.begin(); } return xs.size(); }
void solve() { W = compress(X1, X2, W); H = compress(Y1, Y2, H);
memset(fld, 0, sizeof(fld)); for(int i = 0; i < N; i++) { for(int y = Y1[i]; y <= Y2[i]; y++) { for(int x = X1[i]; x <= X2[i]; x++) { fld[y][x] = true; } } }
int ans = 0; for(int y = 0; y < H; y++) { for(int x = 0; x < W; x++) { if(fld[y][x]) continue; ans++;
queue<pair<int, int> > que; que.push(make_pair(x, y)); while(!que.empty()) { int sx = que.front().first, sy = que.front().second; que.pop(); for(int i = 0; i < 4; i++) { int tx = sx + dx[i], ty = sy + dy[i]; if(tx < 0 || tx >= W || ty < 0 || ty >= H) continue; if(fld[ty][tx]) continue; que.push(make_pair(tx, ty)); fld[ty][tx] = true; } } } } printf("%d\n", ans); }
|
Ps:这里再提一下关于lower_bound
和upper_bound
的用法
lower_bound()
和upper_bound()
都在利用二分查找的方法在一个排好序的数组中进行查找的。
在从小到大的排序数组中:
lower_bound(begin, end, num):
从数组的begin
位置到end-1
位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到数字在数组中的下标。
upper_bound(begin, end, num):
从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。
在从大到小的排序数组中,重载upper_bound()
和lower_bound()
lower_bound( begin,end,num,greater<type>() )
:从数组的begin
位置到end-1
位置二分查找第一个小于或等于num
的数字,找到返回该数字的地址,不存在则返回end。
upper_bound( begin,end,num,greater<type>() )
:从数组的begin
位置到end-1
位置二分查找第一个小于num
的数字,找到返回该数字的地址,不存在则返回end
。