关于离散化算法的看法与感悟
在之前,我一直不理解什么是离散化?什么情况下要进行离散化操作?但今天在洛谷上做了一道关于离散化的题目:P1955 [NOI2015] 程序自动分析 - 洛谷,我用寻常的ac不了,于是我就开始看题解以及向deepseek提问,终于理解了什么是离散化操作。下面将基于洛谷上的一篇题解:题解 P1955 【[NOI2015]程序自动分析】 - 洛谷专栏,以及deepseek来介绍一下什么是离散化算法;
离散化操作在算法中的解释
离散化(Discretization)是算法中一种常见的数据预处理技术,主要用于将连续数据转换为离散数据或将大范围数据映射到小范围区间。
什么是离散化
离散化本质上是一种数据压缩技术,它:
-
将无限或大范围的连续数据映射到有限的小范围离散值
-
保持原始数据的相对顺序关系
-
减少数据的复杂度同时保留关键信息
离散化的常见场景
-
处理大范围数据:当数据范围很大但实际不同值不多时(如1e9范围但只有1e5个不同数)
-
降低算法复杂度:某些算法(如树状数组、线段树)需要数据范围较小
-
将连续数据分类:如将年龄分段为"儿童"、"青年"、"中年"、"老年"
离散化的基本步骤(离散化三部曲)
-
收集所有需要离散化的值
-
排序(通常需要去重)
-
建立映射关系(通常映射到从0或1开始的连续整数
离散化操作一般搭配着unique函数(去重),以及lower_bound函数使用,在下面的代码中,我会解释一下我所理解的用完这两个函数之后会达到一个怎样的效果。
就拿P1955 [NOI2015] 程序自动分析 - 洛谷 该题举例为什么要用离散化操作,以及如何进行离散化操作。
题目描述
在实现程序自动分析的过程中,常常需要判定一些约束条件是否能被同时满足。
考虑一个约束满足问题的简化版本:假设 x1,x2,x3,⋯ 代表程序中出现的变量,给定 n 个形如 xi=xj 或 xi=xj 的变量相等/不等的约束条件,请判定是否可以分别为每一个变量赋予恰当的值,使得上述所有约束条件同时被满足。例如,一个问题中的约束条件为:x1=x2,x2=x3,x3=x4,x4=x1,这些约束条件显然是不可能同时被满足的,因此这个问题应判定为不可被满足。
现在给出一些约束满足问题,请分别对它们进行判定。
输入格式
输入的第一行包含一个正整数 t,表示需要判定的问题个数。注意这些问题之间是相互独立的。
对于每个问题,包含若干行:
第一行包含一个正整数 n,表示该问题中需要被满足的约束条件个数。接下来 n 行,每行包括三个整数 i,j,e,描述一个相等/不等的约束条件,相邻整数之间用单个空格隔开。若 e=1,则该约束条件为 xi=xj。若e=0,则该约束条件为 xi=xj。
输出格式
输出包括 t 行。
输出文件的第 k 行输出一个字符串 YES
或者 NO
(字母全部大写),YES
表示输入中的第 k 个问题判定为可以被满足,NO
表示不可被满足。
首先给出我第一次写的只有60分代码:
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;struct node{int i,j;
};int flag = 0;
vector<int> vis(1000000);
void dfs(unordered_map<int,vector<int>>& ops,int s,int e){vis[s] = 1;int len = ops[s].size();for (int i = 0;i < len;++i){if (!vis[ops[s][i]]){if (ops[s][i] == e)flag = 1;dfs(ops,ops[s][i],e);}}
}
int main()
{int t;cin>>t;for (int i = 0;i < t;++i){int n;cin>>n;flag = 0;unordered_map<int,vector<int>> ops;vector<node> arr;vis.assign(vis.size(),0);for (int j = 0;j < n;++j){int a,b,c;cin>>a>>b>>c;if (c){ops[a].push_back(b);ops[b].push_back(a);}else{arr.push_back({a,b});}}int len = arr.size();for (int j = 0;j < len;++j){dfs(ops,arr[j].i,arr[j].j);}if (flag){cout<<"NO"<<endl;}else{cout<<"YES"<<endl;}}return 0;
}
在该代码里我用的是dfs深度搜索来代替并查集去判断两个数是否在同一连通分量中,将输入的数据一一映射到vis数组中,来记录每一个数据是否被访问过。
但是为什么只有60分呢?可以看一下题目给出的输入的数据的范围:
由图可见数据范围上限是10^9,但是我的vis数组最多也不可能开那么大,去一一映射他输入的数据,因此并不能通过全部测试案例。所以这时候就需要通过用离散化操作来将输入的数据一一映射到一个数组能容纳的大小范围里。
修改过后的代码如下:
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;struct node{int i,j,e;
}arr[100010];
bool cmp(node a,node b){return a.e > b.e;
}int f[200000],book[300000];//初始化并查集
void Init(int len){for (int i = 0;i < len;++i){f[i] = i;}
}
//"查"操作
int find(int x){if (f[x] == x)return x;return f[x] = find(f[x]);//压缩路径
}
//"并"操作
void Union(int x,int y){f[find(x)] = find(y);
}
int main()
{int t;cin>>t;for (int i = 0;i < t;++i){int n;cin>>n;memset(f,0,sizeof(f));memset(book,0,sizeof(book));memset(arr,0,sizeof(arr));int count = 0;for (int i = 0;i < n;++i){//录入原数据cin>>arr[i].i>>arr[i].j>>arr[i].e;book[count++] = arr[i].i;book[count++] = arr[i].j;}//将book数组排序sort(book,book + count);//用unique去重(unique返回的是去重后的数列的尾指针)//len为对book数组去重后的数据量int len = unique(book,book + count) - book;//将book里的原数据一一映射为编号从0到len的形式for (int i = 0;i < n;++i){//寻找原数据进行离散化后的对应编号arr[i].i = lower_bound(book,book + len,arr[i].i) - book;arr[i].j = lower_bound(book,book + len,arr[i].j) - book;}//结束离散化,接下来是正常操作int flag = 0;Init(len);//初始化并查集sort(arr,arr + n,cmp);for (int i = 0;i < n;++i){int x = find(arr[i].i);int y = find(arr[i].j);if (arr[i].e){Union(x,y);}else{if (x == y){flag = 1;break;}}}if (flag){cout<<"NO"<<endl;}else{cout<<"YES"<<endl;}}return 0;
}
如果看不太懂的话可以移步题解 P1955 【[NOI2015]程序自动分析】 - 洛谷专栏。该篇只是记录一下我基于该题对于离散化操作的看法及感悟。