问题描述:
给一系列操作,每个操作是新增或删除一个高维点(1-5维),给出每次操作后现有点集里两点的最大曼哈顿距离。曼哈顿距离是指两个点的一范数,即每个维度差的绝对值之和。
输入:
第一行有两个数,第一个数是n(<=1e5)个操作,第二个数是每个点的维度d(<=5)。
接下来每行为一个操作,用0和1区分删除还是新增,1表示删除,0表示新增。如果是新增,则后面跟着d个整数,如果是删除,后面跟着一个整数c表示删除第c个操作(该操作一定是新增且没被删除过,删除后点集不为空)
输出:
共n行,每行是当前点集中最大曼哈顿距离(若只有一个点,输出0)
提示:
本题对复杂度要求较高,如遇困难,请大家自行搜索“最大曼哈顿距离”算法
可以使用multiset、map、priority_queue
通过二进制枚举符号的情况。
每插入一个节点都要询问最大值和最小值,因此用一个优先队列或者堆维护就可以了。
最粗暴的方法,用abs遍历所有点的复杂度是O(n^2),跑一下肯定是过不了的。搜索一下“最大曼哈顿距离”算法,发现普遍次啊用二进制枚举符号的方法:
举例:假设有两个点:(1,2,3),(4,5,6) 。对于这两个元组,实际的距离应该是:abs(1-4)+abs(2-5)+abs(3-6)。如果把绝对值去掉,遍历每一个维度数的加减情况:
(1+2+3)-(4+5+6)、(-1+2+3)-(-4+5+6)、(-1-2+3)-(-4-5+6)、、、(4+5+6)-(1+2+3)
一共有2^dem种情况
这样对于每一个点(x,y,z,w..)我们把绝对值去掉之后,一定可以把每个点的每个维度整理到一块:(x1-y1+z1)-(x2-y2+z2) 对应二进制 101:1代表正,0代表负 符号排列 一共有2^5种选择
枚举这个信息,每个对应的状态下都会有一个确定的最大值和最小值
例如,上面两个点,111状态下,最大值点是 4+5+6 ,最小状态点是1+2+3
而二者之差就是当前维度符号状态下,最大曼哈顿距离
如果点多了起来,那么也是去最大与最小只差为当前维度符号状态的最大值,复杂度瞬间就低了
遍历所有符号状态,即可找到所有维度符号状态下的最大曼哈顿距离
算法的复杂度是2^dem*O(n)=O(n)
算法实现:
#include <iostream>
#include <map>
#include <queue>
#include <set>
using namespace std;
int renew_prq(map<int, map<int, int> > p,int i,int j,int dem)
{
int sum = 0;
for (int k = 0; k < dem; k++) { //dem维度,考察每个点、每个维度下的符号状态
int t = i & (1 << k); //i与2^k进行与运算,
//例如i=110 则k=0,运算结果是0; k=1ork=2 运算结果是1
//效果是判断i的第k为是否为1
if (t) sum += p[j][k]; //如果为1,则累加入sum 反之,则减去
else sum -= p[j][k];
}
return sum;
}
int main() {
int n,dem;
cin>>n>>dem;
map<int, map<int, int> > Manhatdata;
int data_now;
int instruction;
int ddem=1 <<dem;
int *result = new int[n];
//构造2^dem个 大顶堆,存放每一个维度符号状态下的数据点最大值和最小值(rbegin)
multiset<int> *min_ms= new multiset<int>[ddem]; //小顶堆 begin是最小的
//multiset<int, greater<int>> max_ms[ddem]; //begin 是最大的
for(int ii=1;ii<=n;ii++) //循环输入n个操作
{
int maxManhattan=0;
cin>>instruction;
if(instruction) //如果指令是1,则删除第ist_i个指令插入的数
{
int ist_i;
cin>>ist_i;
//multiset<int>::iterator maxp,minp;
for (int i = 0; i < ddem; i++) { //用二进制i 遍历所有可能的维度符号状态
int sum = 0;
for (int k = 0; k < dem; k++) { //dem维度,考察每个点、每个维度下的符号状态
int t = i & (1 << k); //i与2^k进行与运算, 例如i=110 则k=0,运算结果是0; k=1,k=2 运算结果是1
//效果是判断i的第k为是否为1
if (t) sum += Manhatdata[ist_i][k]; //如果为1,则累加入sum 反之,则减去
else sum -= Manhatdata[ist_i][k];
}
//max_ms[i].erase(tpn);
min_ms[i].erase(min_ms[i].find(sum));
}
Manhatdata.erase(ist_i);
//cout<<maxManhattan<<endl;
}
else //如果指令是0,则插入数组data_now
{
//multiset<int>::iterator maxp,minp;
for(int dem_i=0;dem_i<dem;dem_i++) {
cin>>data_now;
Manhatdata[ii][dem_i]=data_now;
}
for (int i = 0; i < ddem; i++) { //用二进制i 遍历所有可能的维度符号状态
int sum = 0;
for (int k = 0; k < dem; k++) { //dem维度,考察每个点、每个维度下的符号状态
int t = i & (1 << k); //i与2^k进行与运算, 例如i=110 则k=0,运算结果是0; k=1,k=2 运算结果是1
//效果是判断i的第k为是否为1
if (t) sum += Manhatdata[ii][k]; //如果为1,则累加入sum 反之,则减去
else sum -= Manhatdata[ii][k];
}
min_ms[i].insert(sum);
}
//cout<<maxManhattan<<endl;
}
for (int i = 0; i < ddem; i++)//插入或删除后,更新最大曼哈顿距离
{
int maxtan_now=(*min_ms[i].rbegin())-(*min_ms[i].begin());
if(maxtan_now>maxManhattan) maxManhattan=maxtan_now;
}
result[ii-1] = maxManhattan;
}
for (int k=0; k<n; k++) printf("%d\n",result[k]);
cin>>n;
return 0;
}
这里主函数写的比较乱,因为把所有内容都写进去了,包括删除或插入操作后的小顶堆更新、指令更新后的最大曼哈顿距离更新。上面的函数renew_prq是更新小顶堆的操作,没有在主函数种调用是因为会超时,(可能因为函数栈的原因),然后把里面内容拆开放到主函数里就不会超时了,挺离谱的,这个debug半天才试出来,很崩溃。
对了,可以用printf和scanf代替cout和cin,速度会快一些。
这个代码是jqs老师2021-2022秋季学期数据结构课程OJ作业,如果看到有一样的代码是我本人写的,不是抄袭,老师助教明察。
同学们理性参考。
参考博客:【POJ2926】最大曼哈顿距离,参考了其中用二进制穷举遍历所有点的算法思路。