Codeforces Round #397 by Kaspersky Lab and Barcelona Bootcamp (Div. 1 + Div. 2 combined) F

50 篇文章 0 订阅
25 篇文章 0 订阅

题意:

Artsem想买一些纪念品给他的两个队友,现在有n家商店排成一排,每家商店固定出售一个价值的物品,Artsem每次任意选择一个区间的商店,他会在这个区间内选择两家不同的商店,购买这两样纪念品,为了防止他的队友们互相嫉妒,,Artsem希望他买的两种纪念品的价值的差的绝对值最小,共m次询问

n <= 1E5 m <= 3E5


solution:

先把所有询问离线,每个询问按照右端点排序,维护一个指针从左到右扫描一遍,每次把当前的物品加入,维护左端点在各个位置的最优解,再用某种方法去查询,然后,从右到左再进行一次这样的操作

因为左右各扫了一遍,就只讨论从左往右扫描的情形,反之亦然

加入第i个物品的时候,不妨只考虑i左边,价值不低于i的物品,假设物品i的价值为x,从i往左,第一个价值不低于i的物品在位置j,价值为y,那么,对于左端点∈[1,j]的所有区间,答案可以尝试更新为y - x,因为这些区间都包含了i,j两个物品。下面是一个很重要的不等式,假设在物品j左边第一个价值为y'且满足关系y' - x < y - y'的物品位置在j',那么,左端点∈[1,j']的所有区间答案就可以尝试更新为y' - x了,因为这些区间都包含物品i,j',显然,为了这样的更新有意义,需满足y' >= x。

从左到右扫描添加物品的时候,不断执行上述寻找j'的操作,经过一定次数的更新以后,每个区间的答案也就统计出来了,这样就可以回答右端点在当前位置的询问了

先假设总是能够快速找到满足条件的j',那么需要找的j'会很多么?因为能更新答案(也就是决策更优)的物品j',必须满足y' - x < y - y'(否则y和y'组成的答案就并不劣于y'和x了,不值得更新),所以y'一定在区间[x,y]的中点偏左部位,也就是每次值域至少缩减了一半,而物品价值上限是10^9,因此每次这样的更新不超过O(log10^9)次

现在需要回答的问题就转变成,快速找到在区间[1,k]内权值属于[x,y]的最右边的那个点的位置在哪里?

可以考虑从左往右添加的时候维护一棵主席树,这样拿出k这个位置的主席树,在值域上直接查找就行了

至于区间覆盖单点查询之类的操作都是线段树经典问题了。。。

总复杂度O(nlognlog10^9 + mlogn),常数可能比较大(因为要处理两边,还要确定值域)

于是就在cf卡了好久常数。。。。最后是用指针写了主席树加速寻址才卡过去的(跑了2901ms。。。。)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<ctime>
using namespace std;

const int maxn = 1E5 + 10;
const int T = 20;
const int INF = 1E9 + 233;

struct data{
	int pos,Num; data(){}
	data(int pos,int Num): pos(pos),Num(Num){}
};

struct Node{
	int pos; Node *lc,*rc;
}p1[maxn*T],p2[maxn*T],*rt[maxn],*t1,*t2;

int n,m,cnt,cur = 1,A[maxn],a[maxn],Min[maxn*4],Mark[maxn*4],Ans[3*maxn];

vector <data> vl[maxn],vr[maxn];

void Insert(Node *o1,Node *o2,int l,int r,int k,int p)
{
	o2->pos = p; if (l == r) return; int mid = l + r >> 1;
	if (k <= mid)
	{
		o2->lc = ++t1;
		if (o1 == NULL) Insert(NULL,o2->lc,l,mid,k,p),o2->rc = NULL;
		else Insert(o1->lc,o2->lc,l,mid,k,p),o2->rc = o1->rc;
	}
	else
	{
		o2->rc = ++t1;
		if (o1 == NULL) Insert(NULL,o2->rc,mid+1,r,k,p),o2->lc = NULL;
		else Insert(o1->rc,o2->rc,mid+1,r,k,p),o2->lc = o1->lc;
	}
}

void Insert2(Node *o1,Node *o2,int l,int r,int k,int p)
{
	o2->pos = p; if (l == r) return; int mid = l + r >> 1;
	if (k <= mid)
	{
		o2->lc = ++t2;
		if (o1 == NULL) Insert(NULL,o2->lc,l,mid,k,p),o2->rc = NULL;
		else Insert2(o1->lc,o2->lc,l,mid,k,p),o2->rc = o1->rc;
	}
	else
	{
		o2->rc = ++t2;
		if (o1 == NULL) Insert(NULL,o2->rc,mid+1,r,k,p),o2->lc = NULL;
		else Insert2(o1->rc,o2->rc,mid+1,r,k,p),o2->lc = o1->lc;
	}
}

void pushdown(int o,int l,int r)
{
	if (Mark[o] == INF) return;
	Min[o] = min(Min[o],Mark[o]);
	if (l == r) {Mark[o] = INF; return;}
	Mark[o<<1] = min(Mark[o<<1],Mark[o]);
	Mark[o<<1|1] = min(Mark[o<<1|1],Mark[o]);
	Mark[o] = INF; return;
}

void Build(int o,int l,int r)
{
	Min[o] = Mark[o] = INF;
	if (l == r) return; int mid = l + r >> 1;
	Build(o<<1,l,mid); Build(o<<1|1,mid+1,r);
}

void Modify(int o,int l,int r,int ql,int qr,int k)
{
	if (ql <= l && r <= qr)
	{
		Mark[o] = min(Mark[o],k);
		pushdown(o,l,r); return;
	}
	int mid = l + r >> 1; pushdown(o,l,r);
	if (ql <= mid) Modify(o<<1,l,mid,ql,qr,k);
	if (qr > mid) Modify(o<<1|1,mid+1,r,ql,qr,k);
}

int query(int o,int l,int r,int k)
{
	pushdown(o,l,r);
	if (l == r) return Min[o]; int mid = l + r >> 1;
	if (k <= mid) return query(o<<1,l,mid,k);
	else return query(o<<1|1,mid+1,r,k);
}

int Query_Right(Node *o,int l,int r,int ql,int qr)
{
	if (o == NULL) return 0;
	if (ql <= l && r <= qr) return o->pos;
	int mid = l + r >> 1,ret = 0;
	if (ql <= mid) ret = Query_Right(o->lc,l,mid,ql,qr);
	if (qr > mid) ret = max(ret,Query_Right(o->rc,mid+1,r,ql,qr));
	return ret;
}

int Query_Left(Node *o,int l,int r,int ql,int qr)
{
	if (o == NULL) return INF;
	if (ql <= l && r <= qr) return o->pos;
	int mid = l + r >> 1,ret = INF;
	if (ql <= mid) ret = Query_Left(o->lc,l,mid,ql,qr);
	if (qr > mid) ret = min(ret,Query_Left(o->rc,mid+1,r,ql,qr));
	return ret;
}

void Solve1()
{
	Build(1,1,n);
	for (int i = 1; i <= n; i++)
	{
		rt[i] = ++t1;
		Insert(rt[i-1],rt[i],1,cur,a[i],i);
		int k = i - 1,Max = cur;
		for (; k; --k)
		{
			k = Query_Right(rt[k],1,cur,a[i],Max); if (!k) break;
			Modify(1,1,n,1,k,A[a[k]] - A[a[i]]); if (a[k] == a[i]) break;
			int Nex = A[Max] + A[a[i]] >> 1;
			Nex = lower_bound(A + 1,A + cur + 1,Nex) - A;
			while (Nex >= a[i] && A[Max] - A[Nex] <= A[Nex] - A[a[i]]) --Nex;
			if (Nex < a[i]) break; Max = Nex;
		}
		for (int j = 0; j < vr[i].size(); j++)
		{
			data D = vr[i][j];
			Ans[D.Num] = min(Ans[D.Num],query(1,1,n,D.pos));
		}
	}
}

void Solve2()
{
	Build(1,1,n);
	for (int i = n; i; i--)
	{
		rt[i] = ++t2;
		Insert2(rt[i+1],rt[i],1,cur,a[i],i);
		int k = i + 1,Max = cur;
		for (; k <= n; ++k)
		{
			k = Query_Left(rt[k],1,cur,a[i],Max); if (k == INF) break;
			Modify(1,1,n,k,n,A[a[k]] - A[a[i]]); if (a[k] == a[i]) break;
			int Nex = A[Max] + A[a[i]] >> 1;
			Nex = lower_bound(A + 1,A + cur + 1,Nex) - A;
			while (Nex >= a[i] && A[Max] - A[Nex] <= A[Nex] - A[a[i]]) --Nex;
			if (Nex < a[i]) break; Max = Nex;
		}
		for (int j = 0; j < vl[i].size(); j++)
		{
			data D = vl[i][j];
			Ans[D.Num] = min(Ans[D.Num],query(1,1,n,D.pos));
		}
	}
}

int getint()
{
	char ch = getchar(); int ret = 0;
	while (ch < '0' || '9' < ch) ch = getchar();
	while ('0' <= ch && ch <= '9')
		ret = ret * 10 + ch - '0',ch = getchar();
	return ret;
}

int main()
{
	#ifdef DMC
		freopen("DMC.txt","r",stdin);
		//freopen("test.txt","w",stdout);
	#endif
	
	n = getint(); t1 = p1; t2 = p2;
	for (int i = 1; i <= n; i++) A[i] = a[i] = getint();
	sort(A + 1,A + n + 1);
	for (int i = 2; i <= n; i++)
		if (A[i] != A[i-1]) A[++cur] = A[i];
	for (int i = 1; i <= n; i++)
		a[i] = lower_bound(A + 1,A + cur + 1,a[i]) - A;
	cin >> m;
	for (int i = 1; i <= m; i++)
	{
		int l = getint(),r = getint(); Ans[i] = INF;
		vl[l].push_back(data(r,i));
		vr[r].push_back(data(l,i));
	}
	Solve1(); Solve2();
	for (int i = 1; i <= m; i++) printf("%d\n",Ans[i]);
	//cerr << (double)clock() / CLOCKS_PER_SEC << endl;
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值