主要出现在有double的区间问题(建区间树时)和一些虽然是整型但是范围太大时等(无法保存或者枚举),我们可以发现,范围中很大部分数是相同的,那我们就可以保存区间端点或者范围边界来达到缩小数据的范围。
具体做法是把区间排序去重,然后每两个相邻坐标就能形成新的离散化后的区间。
三道题:
第一题: 221 uva,纯离散化,暴力枚举。
输入俯视图中每个建筑物左下角坐标(即x、y坐标中的最小值)、宽度(x方向长度)、深度(y方向长度)、和高度(以上均为实数)。输出正视图中所有能看到的建筑物的编号,按先x后y从小到大排序。
差不多就这个样子
/*
*
* Author : Triose
* Email : Triose@163.com
* Update_time : 2016.6.12
*
*/
//#include<bits/stdc++.h>
#include<stdio.h>
#include<iostream>
#include<string>
#include<string.h>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<iterator>
#include<math.h>
#include<stdlib.h>
#include<time.h>
#include<map>
#include<set>
using namespace std;
//#define ONLINE_JUDGE
#define eps 1e-8
#define inf 0x3f3f3f3f
#define INF 0x7fffffff
#define INFL 0x3f3f3f3f3f3f3f3fLL
#define enter putchar(10)
#define rep(i,a,b) for(int i = (a); i < (b); ++i)
#define repe(i,a,b) for(int i = (a); i <= (b); ++i)
#define mem(a,b) (memset((a),b,sizeof(a)))
#define sf(a) scanf("%d",&a)
#define sfI(a) scanf("%I64d",&a)
#define sfd(a,b) scanf("%d%d",&a,&b)
#define sft(a,b,c) scanf("%d%d%d",&a,&b,&c)
#define sfs(a) scanf("%s",a)
#define pf(a) printf("%d\n",a)
#define pfd(a,b) printf("%d %d\n",a,b)
#define pfP(a) printf("%d %d\n",a.fi,a.se)
#define pfs(a) printf("%s\n",a)
#define pfI(a) printf("%I64d\n",a)
#define PR(a,b) pair<a,b>
#define fi first
#define se second
#define LL long long
#define DB double
#define ds(t) int t; sf(t)
const double PI = acos(-1.0);
const double E = exp(1.0);
template<class T> T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }
template<class T> T lcm(T a, T b) { return a / gcd(a, b) * b; }
template<class T> inline T Min(T a, T b) { return a < b ? a : b; }
template<class T> inline T Max(T a, T b) { return a > b ? a : b; }
int n, m;
#define N 110
struct Building {
DB x, y, w, l, h;
int num;
Building(DB X = 0, DB Y = 0, DB W = 0, DB L = 0, DB H = 0, int num_ = 0) : x(X), y(Y), w(W), l(L), h(H), num(num_) {}
friend bool operator < (const Building & a, const Building & b) {
if(a.x == b.x) return a.y < b.y;
return a.x < b.x;
}
friend istream & operator >> (istream & is, Building & b) {
is >> b.x >> b.y >> b.w >> b.l >> b.h;
return is;
}
};
Building b[N];
DB x[N * 2];
bool belong(int i, double pos) { //判断建筑物是否包含区间中点 等价于包含区间
return b[i].x <= pos && b[i].x + b[i].w >= pos;
}
bool visible(int i) { //判断建筑物是否可见
rep(j, 0, m - 1) {
DB mid = (x[j] + x[j + 1]) / 2;
if(!belong(i, mid)) continue;
bool vs = true;
rep(k, 0, n) {
if(belong(k, mid) && b[k].y < b[i].y && b[k].h >= b[i].h) {
vs = false; break;
}
}
if(vs == true) return true;
}
return false;
}
void solve(int cs) {
if(cs != 1) enter;
cout << "For map #" << cs << ", the visible buildings are numbered as follows:\n";
cout << b[0].num;
rep(i, 1, n) {
if(visible(i)) {
cout << " " << b[i].num;
}
}
enter;
}
void Init() {
m = 0;
rep(i, 0, n) {
cin >> b[i];
b[i].num = i + 1;
x[m++] = b[i].x; x[m++] = b[i].x + b[i].w;
}
sort(x, x + m); //区间端点排序
m = unique(x, x + m) - x; //区间端点去重(必须先排序)
sort(b, b + n); //建筑物排序
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
// freopen("Out.txt", "w", stdout);
#endif
int cas = 0;
while(cin >> n && n) {
Init();
solve(++cas);
}
return 0;
}
本来想区间离散化之后构造区间树,然后把建筑物当做区间插入到树中,不过这样不行。因为没法保证判断建筑物b[i]是否可见的时候在建筑物南边的所有b[k]都已经插入到了树中。
poj 2528 Mayor’s posters
离散化 + 区间树
意思不再赘述,大概是有一面无限长的墙,n张海报,按照从左到右从里到外的顺序给出海报的两个端点。问你当海报按照顺序贴上去之后,有多少张海报是没有被完全覆盖的?
和上面那个题很像,不过可以用线段树优化因为输入保证了从左至右从内到外。那么我先贴外面的,再贴里面的(也就是逆着输入顺序贴)就能保证当贴了一张海报并且未被覆盖时,ans 就可以++(因为可能覆盖它的都已经标记在线段树里面了)。另外,由于题目区间范围太大,而n本身不大,需要进行离散化操作。
/*
*
* Author : Triose
* Email : Triose@163.com
* Update_time : 2016.6.12
*
*/
//#include<bits/stdc++.h>
#include<stdio.h>
#include<iostream>
#include<string>
#include<string.h>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<iterator>
#include<math.h>
#include<stdlib.h>
#include<time.h>
#include<map>
#include<set>
using namespace std;
//#define ONLINE_JUDGE
#define eps 1e-8
#define inf 0x3f3f3f3f
#define INF 0x7fffffff
#define INFL 0x3f3f3f3f3f3f3f3fLL
#define enter putchar(10)
#define rep(i,a,b) for(int i = (a); i < (b); ++i)
#define per(i,a,b) for(int i = (a); i >= (b); --i)
#define repe(i,a,b) for(int i = (a); i <= (b); ++i)
#define mem(a,b) (memset((a),b,sizeof(a)))
#define sf(a) scanf("%d",&a)
#define sfI(a) scanf("%I64d",&a)
#define sfd(a,b) scanf("%d%d",&a,&b)
#define sft(a,b,c) scanf("%d%d%d",&a,&b,&c)
#define sfs(a) scanf("%s",a)
#define pf(a) printf("%d\n",a)
#define pfd(a,b) printf("%d %d\n",a,b)
#define pfP(a) printf("%d %d\n",a.fi,a.se)
#define pfs(a) printf("%s\n",a)
#define pfI(a) printf("%I64d\n",a)
#define PR(a,b) pair<a,b>
#define fi first
#define se second
#define LL long long
#define DB double
#define ds(t) int t; sf(t)
const double PI = acos(-1.0);
const double E = exp(1.0);
template<class T> T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }
template<class T> T lcm(T a, T b) { return a / gcd(a, b) * b; }
template<class T> inline T Min(T a, T b) { return a < b ? a : b; }
template<class T> inline T Max(T a, T b) { return a > b ? a : b; }
int n, m;
//最大海报数量
#define N 10010
//最大区间树节点数量
#define M 80010
PR(int, int) a[N]; //保存海报
int xz[N * 2]; //用于离散化操作
int cnt; //离散化后点的个数
struct Elmt { //区间树元素
bool uncovered; //是否未被覆盖
int ls, rs;
Elmt(bool uc = false, int ls_ = 0, int rs_ = 0): uncovered(uc), ls(ls_), rs(rs_) {}
int mid() { return (ls + rs) >> 1; }
void setcov() { uncovered = false; }
};
Elmt sgt[M]; //区间树
//建树操作,并把uncovered 设置成 true(初始化)
void Build(int root, int lf, int rg) { //Build [lf, rg]
sgt[root].uncovered = true;
sgt[root].ls = lf; sgt[root].rs = rg;
if(lf == rg) return ;
int mid = sgt[root].mid();
Build(2 * root + 1, lf, mid);
Build(2 * root + 2, mid + 1, rg);
}
//读入数据,离散化端点,建树
void Init() {
sf(n); cnt = 0;
rep(i, 0, n) {
sfd(a[i].first, a[i].second);
xz[cnt++] = a[i].first;
xz[cnt++] = a[i].second;
}
sort(xz, xz + cnt); cnt = unique(xz, xz + cnt) - xz;
Build(0, 0, cnt - 1);
}
//插入区间(如果左右端点分别为xz[lf] 和 xz[rg] 的海报插入后发现未被覆盖,返回1,否则返回0)
int ins(int root, int lf, int rg) { //root 是当前根, lf 和 rg 分别是 xz 的下标
if(sgt[root].ls == lf && sgt[root].rs == rg) {
if(sgt[root].uncovered) { //如果没被覆盖,返回1,贴上海报之后就被覆盖了(维护区间树)
sgt[root].setcov();
return 1;
}
else { //如果已经被覆盖,直接返回0
return 0;
}
}
if(!sgt[root].uncovered) return 0; //如果比它大的区间都已经被覆盖了,就不用更新什么了,直接返回0
int mid = sgt[root].mid(); //以下是区间树的常规写法。注意维护和返回分别该用些什么操作
if(rg <= mid) {
int ans = ins(root * 2 + 1, lf, rg);
sgt[root].uncovered = (sgt[root * 2 + 1].uncovered || sgt[root * 2 + 2].uncovered);
return ans;
}
else if(lf > mid) {
int ans = ins(root * 2 + 2, lf, rg);
sgt[root].uncovered = (sgt[root * 2 + 1].uncovered || sgt[root * 2 + 2].uncovered);
return ans;
}
int ans = ins(root * 2 + 1, lf, mid) | ins(root * 2 + 2, mid + 1, rg);
sgt[root].uncovered = sgt[root * 2 + 1].uncovered || sgt[root * 2 + 2].uncovered;
return ans;
}
int ID(int* arr, int num, int v) { //给定海报端点返回再xz中的下标
return lower_bound(arr, arr + num, v) - arr;
}
int solve() {
int ans = 0;
per(i, n - 1, 0) {
int l = ID(xz, cnt, a[i].first);
int r = ID(xz, cnt, a[i].second);
ans += ins(0, l, r);
}
return ans;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
// freopen("Out.txt", "w", stdout);
#endif
ds(t);
while(t--) {
Init(); //输入
pf(solve()); //处理
}
return 0;
}
还有uva的一道神题,三维空间的离散化。我现在还没懂透!这已经是第五遍尝试了,终于过了!
12171 uva Sculpture
某雕塑由n(n <= 50)个边平行于坐标轴的长方体组成。 每个长方体用6个整数 x0, y0, z0, x, y, z 表示(均小于500大于0),其中 x0 为顶点中坐标最小的一个, x 表示长方体在 x 方向的长度。 其他 4 个值类似定义。 你的任务是统计这个雕像的体积和表面积。 注意, 雕塑内部可能会有密闭空间, 其体积应计算在总体积中,但从“外部”看不到的面不应计入表面积。 雕塑可能会由多个连通块组成。
直接给代码吧。懒得分析了。留个位置下次分析。
/*
*
* Author : Triose
* Email : Triose@163.com
* Update_time : 2016.6.12
*
*/
//#include<bits/stdc++.h>
#include<stdio.h>
#include<iostream>
#include<string>
#include<string.h>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<iterator>
#include<math.h>
#include<stdlib.h>
#include<time.h>
#include<map>
#include<set>
using namespace std;
//#define ONLINE_JUDGE
#define eps 1e-8
#define inf 0x3f3f3f3f
#define INF 0x7fffffff
#define INFL 0x3f3f3f3f3f3f3f3fLL
#define enter putchar(10)
#define rep(i,a,b) for(int i = (a); i < (b); ++i)
#define per(i,a,b) for(int i = (a); i >= (b); --i)
#define repe(i,a,b) for(int i = (a); i <= (b); ++i)
#define mem(a,b) (memset((a),b,sizeof(a)))
#define sf(a) scanf("%d",&a)
#define sfI(a) scanf("%I64d",&a)
#define sfd(a,b) scanf("%d%d",&a,&b)
#define sft(a,b,c) scanf("%d%d%d",&a,&b,&c)
#define sfs(a) scanf("%s",a)
#define pf(a) printf("%d\n",a)
#define pfd(a,b) printf("%d %d\n",a,b)
#define pfP(a) printf("%d %d\n",a.fi,a.se)
#define pfs(a) printf("%s\n",a)
#define pfI(a) printf("%I64d\n",a)
#define PR(a,b) pair<a,b>
#define fi first
#define se second
#define LL long long
#define DB double
#define ds(t) int t; sf(t)
const double PI = acos(-1.0);
const double E = exp(1.0);
template<class T> T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }
template<class T> T lcm(T a, T b) { return a / gcd(a, b) * b; }
template<class T> inline T Min(T a, T b) { return a < b ? a : b; }
template<class T> inline T Max(T a, T b) { return a > b ? a : b; }
int n, m;
const int maxn = 60;
const int maxc = 1001;
int x[maxn], y[maxn], z[maxn], dx[maxn], dy[maxn], dz[maxn];//原始数据
int spc[maxn * 2][maxn * 2][maxn * 2]; //三维空间
const int dirs[6][3] = {{1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}, {0, 0, 1}, {0, 0, -1}}; //方向
//用于离散化
int xz[maxn * 2], yz[maxn * 2], zz[maxn * 2];
int xcnt, ycnt, zcnt;
//表示空间中离散化后的一个点(也就是三条边)(离散化后一个点表示一条边)
struct Cub {
int x, y, z;
Cub(int x_ = 0, int y_ = 0, int z_ = 0): x(x_), y(y_), z(z_) {}
void setvis() const { spc[x][y][z] = -1; }
bool canvis() const { return spc[x][y][z] != -1;}
bool valid() const { return x < xcnt - 1 && y < ycnt - 1 && z < zcnt - 1 && x >= 0 && y >= 0 && z >= 0; }
bool isboundary() const { return spc[x][y][z] == 1; }
Cub & neighbor(int i) const { return * (new Cub(x + dirs[i][0], y + dirs[i][1], z + dirs[i][2])); }
int volume() const {return (xz[x + 1] - xz[x]) * (yz[y + 1] - yz[y]) * (zz[z + 1] - zz[z]); }
int surface(int i) const {
int xlen = xz[x + 1] - xz[x], ylen = yz[y + 1] - yz[y], zlen = zz[z + 1] - zz[z];
if(dirs[i][0] != 0) return ylen * zlen;
else if(dirs[i][1] != 0) return xlen * zlen;
return xlen * ylen;
}
};
void standerlize(int * arr, int & cnt) {//离散化过程
sort(arr, arr + cnt);
cnt = unique(arr, arr + cnt) - arr;
}
int ID(int * arr, int cnt, int v) { //找位置
return lower_bound(arr, arr + cnt, v) - arr;
}
void mark() { //标记边
rep(i, 0, n) {
int X1 = ID(xz, xcnt, x[i]), X2 = ID(xz, xcnt, dx[i]);
int Y1 = ID(yz, ycnt, y[i]), Y2 = ID(yz, ycnt, dy[i]);
int Z1 = ID(zz, zcnt, z[i]), Z2 = ID(zz, zcnt, dz[i]);
rep(X, X1, X2) rep(Y, Y1, Y2) rep(Z, Z1, Z2) spc[X][Y][Z] = 1;
}
}
void Init() { //初始化
mem(spc, 0); xcnt = 2; ycnt = 2; zcnt = 2;
xz[0] = 0; xz[1] = maxc;
yz[0] = 0; yz[1] = maxc;
zz[0] = 0; zz[1] = maxc;
cin >> n;
rep(i, 0, n) {
cin >> x[i] >> y[i] >> z[i] >> dx[i] >> dy[i] >> dz[i];
dx[i] += x[i]; dy[i] += y[i]; dz[i] += z[i];
xz[xcnt++] = x[i]; xz[xcnt++] = dx[i];
yz[ycnt++] = y[i]; yz[ycnt++] = dy[i];
zz[zcnt++] = z[i]; zz[zcnt++] = dz[i];
}
standerlize(xz, xcnt);
standerlize(yz, ycnt);
standerlize(zz, zcnt);
mark();
}
void floodfill(int & s, int & v) { //种子填充的bfs写法
int maxv = maxc * maxc * maxc;
queue<Cub> q;
Cub p, nxt; q.push(p); p.setvis();
while(!q.empty()) {
p = q.front(); q.pop();
v += p.volume();
rep(i, 0, 6) {
nxt = p.neighbor(i);
if(!nxt.valid()) continue;
if(nxt.isboundary()) {
s += nxt.surface(i);
continue;
}
if(nxt.canvis()) {
nxt.setvis();
q.push(nxt);
}
}
}
v = maxv - v;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
// freopen("Out.txt", "w", stdout);
#endif
ios::sync_with_stdio(false);
int t; cin >> t;
while(t--) {
Init();
int v = 0, s = 0;
floodfill(s, v);
cout << s << " " << v << endl;
}
return 0;
}
这题难在离散化之后一个点表示一条边,本来就是三维的空间,相当于再增加了一个维度,我想象不出来。。。
总结:离散化可以降维度,上题就是把边降成了点,只不过点还是三维的点。但是第二题就确确实实把区间变成了一个点。反正离散化能够把连续的范围大的问题,转换成离散的范围小的问题。