邻值查找

AcWing 136 邻值查找

给定一个长度为 n 的序列 A,A 中的数各不相同。对于 A 中的每一个数 Ai,求:

min1≤j<i |Ai−Aj|
以及令上式取到最小值的 j(记为 Pi)。若最小值点不唯一,则选择使 Aj 较小的那个。

输入格式
第一行输入整数n,代表序列长度。

第二行输入n个整数A1…An,代表序列的具体数值,数值之间用空格隔开。

输出格式
输出共n-1行,每行输出两个整数,数值之间用空格隔开。

分别表示当i取2–n 时,对应的min1≤j<i|Ai−Aj|和Pi的值。

数据范围
n≤105,|Ai|≤109


首先想到的,是维护一个单调的链表,边读边算边输出
这样每一个数进来时,都能保证链表里的数下标比它小,那么插入后用它两边的数计算答案即可

本来想着来个二分,然鹅…
链表这东西怎么二分啊啊啊啊QAQ
然后直接敲了一个交上去,果然TLE

#include <iostream>
#include <cstdio>
using namespace std;
#define N 100005
int b[N];
struct Node
{
	int value, ind;
	Node *prev, *next;
};
Node *head, *tail;
void initialize()
{
	head = new Node();
	tail = new Node();
	head->next = tail;
	tail->prev = head;
}
void insert(Node *p, int val,int i)
{
	Node *q;
	q = new Node();
	q->value = val;
	q->ind = i;
	q->next = p->next;
	p->next->prev = q;
	p->next = q;
	q->prev = p;
}
void remove(Node *p)
{
	p->prev->next = p->next;
	p->next->prev = p->prev;
	delete p;
}
void recycle()
{
	while(head != tail)
	{
		head = head->next;
		delete head->prev;
	}
	delete tail;
}
int abs(int a)
{
	return a > 0 ? a : -a;
}
int main() {
	initialize();
	int n;
	scanf ("%d", &n);
	for(int i= 1; i <= n; ++i)
	{
		int d;
		scanf ("%d", &d);
		Node *p, *q;
		p = tail->prev;
		while (p->value > d && p != head)
		{
			p = p->prev;
		}
		insert (p, d, i); 
		/*Node *k;
		k = head->next;
		while(k != tail)
		{
			printf("%d ", k->value );
			k = k->next;
		}
		printf("\n");*/
		if (i == 1)
			continue;
		q = p->next->next;
		int ans1 = abs(p->value - d), ans2 = abs(q->value - d);
		//printf("%d %d %d %d %d %d\n", p->ind, p->value, q->ind, q->value, ans1, ans2);
		if(p == head || q == tail)//先判断有没有超范围 
		{
			if(p == head)
				printf("%d %d\n", ans2, q->ind );
			else
				printf("%d %d\n", ans1, p->ind );
		}
		else
		{
			printf("%d ", min(ans1, ans2));
			if(ans1 < ans2)
				printf("%d\n", p->ind );
			else if(ans1 > ans2)
				printf("%d\n", q->ind );
			else
				printf("%d\n",p->value < q->value ? p->ind : q->ind );//选A[i]更小的 
		}
	}
	return 0;
}

于是我又想到,好好的vector放在那干嘛不用呢…
( ̄^ ̄゜)尴尬


接下来是链表正解(结合了 算法竞赛进阶指南 和个人理解)

把A从小到大排序,然后依次串成链表。同时建立数组B,其中Bi表示原始序列中Ai处于链表中的哪个位置
(这里注意了,也就是说Bi指向的结点的数值在原数组中下标为i)
接下来我们从大到小循环一遍
利用Ai左右两边的数值算答案(链表有序),再将Ai从链表中删除
(这样相当于按照输入的顺序逆序删除,可以保证算Ai答案时链表里的结点都比Ai先输入,这就是奇妙之处啦)
时间复杂度 O ( n l o g n ) O(n log n) O(nlogn),瓶颈在于排序

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#define N 100005
struct T
{
	int val,in;
}a[N];
bool cmp(T a, T b)
{
	return a.val < b.val;
}
struct Node
{
	int value, ind;
	Node *prev, *next;
}*b[N];
Node *head, *tail;
void initialize()
{
	head = new Node();
	tail = new Node();
	head->next = tail;
	tail->prev = head;
}
void insert(Node *p, int val,int i)
{
	Node *q;
	q = new Node();
	q->value = val;
	q->ind = i;
	q->next = p->next;
	p->next->prev = q;
	p->next = q;
	q->prev = p;
}
void remove(Node *p)
{
	p->prev->next = p->next;
	p->next->prev = p->prev;
	delete p;
}
void recycle()
{
	while(head != tail)
	{
		head = head->next;
		delete head->prev;
	}
	delete tail;
}
int abs(int a)
{
	return a > 0 ? a : -a;
}
int main() {
	initialize();
	int n;
	scanf("%d", &n);
	for(int i = 1; i <= n; ++i)
	{
		scanf("%d", &a[i].val);
		a[i].in = i;
	}
	sort(a + 1, a + n + 1, cmp);
	for(int i = 1; i <= n; ++i)
	{
		insert(tail->prev, a[i].val, a[i].in);
		b[a[i].in] = tail->prev;
		//printf("%d : %d %d\n", i, b[a[i].in ]->ind, b[a[i].in ]->value );
	}
	for(int i = n; i > 1; --i)
	{
		Node *p, *q;
		p = b[i]->prev, q = b[i]->next;
		int ans1 = abs(p->value - b[i]->value), ans2 = abs(q->value - b[i]->value );
		//printf("%d %d %d %d %d %d\n", p->ind, p->value, q->ind, q->value, ans1, ans2);
		if(p == head || q == tail)
		{
			if(p == head)
				a[i].val = ans2, a[i].in = q->ind ;
			else
				a[i].val = ans1, a[i].in = p->ind ;
		}
		else
		{
			if(ans1 > ans2)
				a[i].val = ans2, a[i].in = q->ind ;
			else if(ans1 < ans2)
				a[i].val = ans1, a[i].in = p->ind ;
			else
				a[i].val = ans1, a[i].in = q->value < p->value ? q->ind : p->ind ; 
		}
		remove(b[i]);
	}
	for(int i = 2; i <= n; ++i)//记得要存起来正序输出QAQ 
	{
		printf("%d %d\n", a[i].val, a[i].in);
	}
	return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值