0720-并查集+栈-CF 500 E

 多米诺骨牌

描述

有n个多米诺骨牌,从左到右排列,每一个骨牌都有一个高度Li,向右推倒,它会直接向右倒下,如下图,倒下后该骨牌的顶端落在Xi+Li的位置,(Xi是它位于的坐标,即倒下时该骨牌不会发生移动)

20180428165907_66810

在倒下过程中,骨牌会碰到其他骨牌,碰到的骨牌会向右倒,如下图,最左边的骨牌倒下会碰倒A,B,C,A,B,C会倒下,但是不会直接碰到D,但是D会因为C的倒下而碰倒。

20180428165914_54220

现在给你N个骨牌的坐标Xi,和每个骨牌的高度Li。则一个骨牌能碰倒另一个骨牌当切仅当xi+li≥xj。同时有Q个询问 [L,R],问向右推到第L个骨牌,最少需要多少代价让R倒下。你可以临时增加某个骨牌的高度,增加1个高度的代价是1.

输入

第一行是一个整数N,表示共N个骨牌

接下来N行描述每个骨牌的信息Xi,Li表示每个骨牌的位置与高度,保证Xi<Xi+1

接下来一行是一个整数Q,表示Q个询问

接下来是Q行,表示每个询问L,R,保证1<=L<R<=N

输出

对于每个询问,分Q行打印答案

样例输入

6
1 5
3 3
4 4
9 2
10 1
12 1
4
1 2
2 4
2 5
2 6

样例输出

0
1
1
2

提示

20180428165952_21465

数据规模

20%数据:N,Q<=1000,Xi<=10000

40%数据:N,Q<=10000,Xi<=100000

100%数据:2<=N<=100000,1<=Q<=200000,Xi<=10^9


题意理解

这道题暴力是很容易拿到40分的(因为数据水了)

但蒟蒻竟然连题目意思都搞错了,最后抱了个0鸭蛋回家

问向右推到第L个骨牌,最少需要多少代价让R倒下。”  若按我的理解就是从1开始推到L,然后问还需要多少代价会使R倒下

但实际上是只把L推倒,所以个人意见把题改为“问向右推第L个骨牌,最少需要多少代价让R倒下。”更清楚些


分析

由于数据范围较大,特别是询问有200000次,如果每次都现求的话,肯定不现实,所以我们肯定需要什么东西进行预处理,然后O(1)查询

我们可以把题目转化为  求某一个区间内没有被覆盖到的长度是多少  然后用后缀和进行处理

首先定义 sum [ i ] 表示第 i 个骨牌到最后一个骨牌之间,不能被覆盖到的长度

然后可以利用并查集的思想,把中间没有断点(也就是说前一个骨牌倒下可以推倒这一个)的骨牌看作是一个整体,保留这个整体最左边的点

那么询问时,对于第二个骨牌,我们就去寻找他的祖先(他所在整体的最左边的骨牌),( i 表示第一个骨牌,j 表示第二个骨牌 ) 然后sum [ i ] - sum [ f i n d (j) ]就是答案了


代码

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
#define N 100009            
using namespace std;
int n,Q;
struct node{
	int l,r;
}a[N],b[2*N];
int fa[N];

inline int read(){
	char ch;int f=1;
	while((ch=getchar())<'0'||ch>'9')
		if(ch=='-') f=-1;
	int res=0;
	while(ch>='0'&&ch<='9'){
		res=res*10+ch-'0';
		ch=getchar();
	}
	return f*res;
}
vector<int> q[N];
stack<int> S;
long long  ans[2*N],sum[N];
int find(int x){//并查集找祖先的函数
	if(x!=fa[x]) fa[x]=find(fa[x]);
//记住一句话:如果我不是我自己的爸爸,那我的爸爸就是我爸爸的爸爸
	return fa[x];
}
void solve(int x,int id){
	int temp=b[id].r;
	ans[id]=sum[x]-sum[find(temp)];//后缀和
}
int main(){
	n=read();
	int i,j,k;
	for(i=1;i<=n;++i) fa[i]=i;
	for(i=1;i<=n;++i){
		a[i].l=read();
		a[i].r=a[i].l+read();//第i个骨牌的左端点,以及能到达的右端点 
	}
	Q=read();
	for(i=1;i<=Q;++i){//对于数据范围,可以从循环里去检验(这就是为什么b数组是 2*n 的原因了)
		b[i].l=read();
		b[i].r=read();
		q[b[i].l].push_back(i);
	}
	for(i=n;i>=1;--i){
		while(!S.empty()&&a[i].r>=a[S.top()].l){//如果属于一个整体
			fa[find(S.top())]=i;
			a[i].r=max(a[S.top()].r,a[i].r);
			S.pop();
		}
		if(!S.empty()) sum[i]=sum[S.top()]+a[S.top()].l-a[i].r;//如果中间有断点,就更新
		else sum[i]=0;
		S.push(i);//用栈来存储
		int len=q[i].size();
		for(j=0;j<len;++j){
			solve(i,q[i][j]);
		}
	} 
	for(i=1;i<=Q;++i)
		printf("%lld\n",ans[i]);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_42557561/article/details/81126966