【数据结构】最大曼哈顿距离

问题描述:

给一系列操作,每个操作是新增或删除一个高维点(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】最大曼哈顿距离,参考了其中用二进制穷举遍历所有点的算法思路。

csdn:【POJ2926】最大曼哈顿距离icon-default.png?t=LBL2https://blog.csdn.net/weixin_41863129/article/details/89196887?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164145982516781683929114%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=164145982516781683929114&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-89196887.first_rank_v2_pc_rank_v29&utm_term=%E3%80%90POJ2926%E3%80%91%E6%9C%80%E5%A4%A7%E6%9B%BC%E5%93%88%E9%A1%BF%E8%B7%9D%E7%A6%BB&spm=1018.2226.3001.4187

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值