【2018/07/19测试T3】【SDOJ 3124】多米诺骨牌

【题目】

题目传送门New Year Domino

题目描述:

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

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

下图也是倒下的多米诺骨牌的一个例子,红色圆圈代表两个多米诺骨牌的接触

现在给你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行打印答案

样例数据:

输入

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

1 2 
2 4 
2 5 
2 6

输出
0
1
1
2

备注:

提示图:

数据规模与约定: 
20%数据:N,Q≤1000,Xi≤10000
40%数据:N,Q≤10000,Xi≤100000
100%数据:2≤N≤100000,1≤Q≤200000,Xi≤10^9

 

【分析】

不由得说,这道题的数据是真的水,我纯暴力竟然过了40分

好多人这道题都是用的离散化+线段树,我们老师给我们介绍的是并查集的做法

首先呢,这道题我们可以转换一下,我们可以把他当做 n 段序列,每一段的左端点为Xi,右端点为Xi+Li,每次的询问相当于在问 l 到 r 之间没有覆盖到的点的总数,这个数就是答案

我们考虑并查集,如果左边的骨牌能碰到右边的骨牌,就把它们合并成一个更大的骨牌,再来统计答案

我们从右往左枚举骨牌,用栈来记录,从右往左是因为左边可能有很高的骨牌能碰到栈顶的骨牌以及栈顶碰不到的骨牌

我们用 sum[ i ] 记录 i 到最右边的骨牌要用到的代价,那答案就是 sum[ l ] - sum[ find( r ) ] ,find 是并查集的操作

另外,我的做法是一个离线的做法,每次要记录一下左端点,右端点和编号(即 i)

大佬都用的是 vector,但是蒟蒻不会,我用的是前向星

具体怎么做还是看代码吧

 

【代码】

#include<stack>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1000005;
int x[N],y[N],p[N],q[N];
int t,id[N],first[N],v[N],next[N];
int ans[N],sta[N],sum[N],father[N];
int find(int x)
{
	if(father[x]!=x)
	  father[x]=find(father[x]);
	return father[x];
}
void add(int l,int r,int k)
{
	t++;
	next[t]=first[l];
	first[l]=t;
	v[t]=r;
	id[t]=k;
}
int main()
{
//	freopen("domino.in","r",stdin);
//	freopen("domino.out","w",stdout);
	int n,m,i,j,l,r,top=0;
	scanf("%d",&n);
	for(i=1;i<=n;++i)
	{
		scanf("%d%d",&l,&r);
		x[i]=l;
		y[i]=l+r;
		father[i]=i;         //并查集初始化 
	}
	scanf("%d",&m);
	for(i=1;i<=m;++i)
	{
		scanf("%d%d",&l,&r);
		add(l,r,i);          //用前向星存储 
	}
	for(i=n;i>=1;--i)
	{
		while(top&&y[i]>=x[sta[top]])  //如果i这个骨牌能碰到栈顶的骨牌 
		{
			y[i]=max(y[i],y[sta[top]]);//合并骨牌,右端点是较大的那个 
			father[find(sta[top])]=i;  //并查集 
			top--; //弹出栈 
		}
		if(top)
		  sum[i]=sum[sta[top]]+x[sta[top]]-y[i]; //记录后缀和 
		sta[++top]=i;        //将i压入栈 
		for(j=first[i];j;j=next[j])    //枚举以i为起点的询问并记录答案 
		  ans[id[j]]=sum[i]-sum[find(v[j])];
	}
	for(i=1;i<=m;++i)
	  printf("%d\n",ans[i]);
//	fclose(stdin);
//	fclose(stdout);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值