Description
Input
Output
Sample Input
1 5 1 4 2 6 8 10 3 4 7 10
Sample Output
4
第一次写线段树离散化的题,记录下学习感受(主要是个人对于离散化的理解):
此题很显然的线段树的题目,但是区间的大小有10^7那么大,正常写必然MLE(同时也是TLE)
但是其实如果区间只有2个分别是[1,999]和[200,9999],我们可以将区间换成[1,3][2,4]
为什么可以这样?这是怎么转换的?不会出问题吗?
于是引入了离散化:
离散化我的理解是 有些数据本身很大,无法直接作为数组下标保存对应的属性
如果此时只需要用到这堆数据的相对属性,而与具体数值无关的时候,可以利用离散化将数据缩小
举个简单栗子:如果有一堆很大的数据,而我只是需要比较它们的大小而不需要考虑它们真实的值,那么
123 、1234 、1234、12345 、12356 这堆数可以用 1、2、2、3、4代替,
因为我需要的性质(123 < 1234 = 1234 < 12345 < 123456)
被保存下来了(1<2=2<3<4)
显然,离散化的特点是数据更小,但是它最关键的条件是:数据的相对属性不变(而且是你只用到相对属性而不用数据的具体数值,或者如果两者都需要用可以自己想办法进行2种保存)
上面的例子并不很生动,于是我们开始以线段树的区间离散化为例:
首先,就这题而言,我们只需要区间的相对属性,而不需要区间的具体长度(这是显然的)
所谓区间的相对属性,主要就是各个区间之间的关系了,包含?交?并?
比如之前举的[1,999]和[200,9999]这两个区间是‘交’的关系,于是我们将它化为[1,3][2,4]也能体现其交的关系
再比如[1,1000]和[5,999]这两个区间可以离散化成[1,4][2,3]
如果是[1,1000][1001,2000]则离散化成[1,2][3,4]
而如果是[1,1000][1000,2000]则离散化成[1,2][2,3](端点相等的性质也要体现出来)
这样就大概懂了什么是离散化了,那么具体怎么操作呢?区间那么多,要怎么离散才能保证所有区间的性质都不变?
这个我想了好久,什么DP、BFS、DFS、各种处理方法瞎想,后来看了大牛的博客才发现处理方法居然那么简单!!!(怪我傻。)
还是[1,999]和[200,9999]这个简单的例子,我们将区间的端点单独拿出来看(从小到大),就是4个数:1,200,999,9999
然后我们将这四个数离散成:1,2,3,4 之后再放回原来的区间,就成了[1,3][2,4]
接下来对样例进行离散化:
原区间:[1,4][2,6][8,10][3,4][7,10]
取数据: 1 2 3 4 4 6 7 8 10 10
离散化: 1 2 3 4 4 5 6 7 8 8
后来的区间: [1,4][2,5][7,8][3,4][6,8]
注意之前提到过的区间端点相等的性质也需要保存
为了单独取出区间端点,我们需要保存该端点是属于哪个区间,是左端点还是右端点,于是开这样一个结构体:
struct Seg { int num;//保存一个端点 int pos;//记录是第几个区间 bool w;//记录是区间的左端点还是右端点,左0右1 }ol[2*N+5]; bool cmp(Seg a,Seg b)//端点排序方便后来离散化处理 { return a.num < b.num; }
离散化之后的区间我又开了一个结构体保存。。。(毕竟第一次玩离散化。。。还太渣。。。)
struct QU { int l,r;//原数据离散化后的新区间 }op[N+5];下面是离散化的具体过程:
for (int i = 1,l,r;i <= n;++i) { scanf("%d %d",&l,&r); ol[++k].num = l,ol[k].pos = i,ol[k].w = 0; ol[++k].num = r,ol[k].pos = i,ol[k].w = 1; } /******************************离散化*************************/ sort(ol+1,ol+k+1,cmp); if (ol[1].w) op[ol[1].pos].r = 1; else op[ol[1].pos].l = 1;//离散化后最小的数化为1 int x = 1; for (int i = 2;i <= k;++i) { if (ol[i].num == ol[i-1].num)//离散后区间性质不能变 { if (ol[i].w) op[ol[i].pos].r = x; else op[ol[i].pos].l = x; } else //因为前面排序了,这里只有可能ol[i].num < ol[i-1].num { x++;//比前一个数大一就可以满足‘大’这个性质 if (ol[i].w) op[ol[i].pos].r = x; else op[ol[i].pos].l = x; } }
#pragma comment(linker, "/STACK:1024000000,1024000000") #include<iostream> #include<cstdio> #include<cstring> #include<string> #include<algorithm> #include<queue> #include<cmath> #include<stdlib.h> #include<cctype> #include<map> #define mem(a,x) memset(a,x,sizeof(a)) #define esp 1e-8 using namespace std; typedef long long ll; const int dx[] = {-1,1,0,0}; const int dy[] = {0,0,-1,1}; const int inf = 1<<29; const int N = 10000; struct Seg { int num;//保存一个端点 int pos;//记录是第几个区间 bool w;//记录是区间的左端点还是右端点,左0右1 }ol[2*N+5]; bool cmp(Seg a,Seg b)//端点排序方便后来离散化处理 { return a.num < b.num; } struct QU { int l,r;//原数据离散化后的新区间 }op[N+5]; const int NN = 200000;//离散化后的数据上限2*N struct Node { int l,r; int k; }q[4*NN]; bool vis[NN+5]; void build(int i,int l,int r) { q[i].l = l,q[i].r = r;q[i].k = 0; if (l == r) { return ; } int mid = (l+r)>>1; build(i<<1,l,mid); build((i<<1)+1,mid+1,r); } void update(int i,int l,int r,int k) { if (q[i].l ==l && r == q[i].r) { q[i].k = k;//该区间贴上第k个人 return ; } if (q[i].k > 0&&q[i].k != k) { q[i<<1].k = q[i].k; q[(i<<1)+1].k = q[i].k; q[i].k = 0; } int mid = (q[i].l + q[i].r)>>1; if (r <= mid) update(i<<1,l,r,k); else if (l > mid) update((i<<1)+1,l,r,k); else { update(i<<1,l,mid,k); update((i<<1)+1,mid+1,r,k); } } int sun; void Cal(int i) { if (q[i].k)//找到该区间贴的人编号 { if (!vis[q[i].k])//如果这个人没有算过 { // cout<<"区间["<<q[i].l<<","<<q[i].r<<"]贴着第"<<q[i].k<<"个人"<<endl; vis[q[i].k] = 1; sun++; } return; } Cal(i<<1); Cal((i<<1)+1); } int main() { int T;scanf("%d",&T); while (T--) { int n; scanf("%d",&n); int k = 0; for (int i = 1,l,r;i <= n;++i) { scanf("%d %d",&l,&r); ol[++k].num = l,ol[k].pos = i,ol[k].w = 0; ol[++k].num = r,ol[k].pos = i,ol[k].w = 1; } /******************************离散化*************************/ sort(ol+1,ol+k+1,cmp); if (ol[1].w) op[ol[1].pos].r = 1; else op[ol[1].pos].l = 1;//离散化后最小的数化为1 int x = 1; for (int i = 2;i <= k;++i) { if (ol[i].num == ol[i-1].num)//离散后区间性质不能变 { if (ol[i].w) op[ol[i].pos].r = x; else op[ol[i].pos].l = x; } else //因为前面排序了,这里只有可能ol[i].num < ol[i-1].num { x++;//比前一个数大一就可以满足‘大’这个性质 if (ol[i].w) op[ol[i].pos].r = x; else op[ol[i].pos].l = x; } } /***************************线段树**************************/ build(1,1,x);//刚刚的离散化使数据上限变成x mem(vis,0);sun = 0; for (int i = 1;i <= n;++i)//贴海报 { update(1,op[i].l,op[i].r,i); }Cal(1);//计算 printf("%d\n",sun); } return 0; }