bzoj2520 [Shoi2010]扫雷机器人

思路摘自cqx大佬思路:
30分的算法
用图论的想法来理解这个问题。每个雷为图论中的一个点,如果i的爆炸会直接导致j的爆炸,那就连一条从i到j的有向边。每一次引爆i,就相当于把图中所有从i可直接、间接到达的点都删除。题目相当于问把所有点都删除至少需要多少次、至多需要多少次这样的操作。这显然只需要先求出强连通块每块缩成一个点。强连通块的个数就是最大值,缩点后没有入度的点数就是最小值。时间复杂度。
此算法可以通过01、02、03、06组数据

标准解法(100分的算法)
如上面图论方法的话,所构出的的图并不是一个普通的图,而有它的一定的特征。我们称缩点后没有入度的强连通块为“父块”,有入度的称为“子块”。所以“父块”数为最小值,“父块”数+“子块”数为最大值。
我们从一个空图开始,从左向右依次把炸弹(其对应的点)加入图中。那么这个新的点x对于图的“块”结构可能产生这样的影响:
(1) 某“块”A可以炸到x(图中可达,下同),且x可以炸到A,那么x会被添加到A中
(2) 某“块”A可以炸到x,且x不可以炸到A,x所在的块成为一个“子块”
(3) 某“块”A不可以炸到x,且x可以炸到A,A成为一个“子块”(如果原来是一个父块的话)
那么某块A能否炸到x如何判断呢?只需比较A爆炸导致的所有直接的或者间接的爆炸的右边界与x的位置。(因为是从左向右把所有炸弹加入图中的)
因此,如果某块不能炸到这个新的x,那么以后就再也炸不到新的炸弹了。这意味着这个块再也不可能与其他的点或者块合并了。从而,如果它已经是一个“子块”,它将永远是一个“子块”(我们不用再管了);如果它是一个“父块”,只需关心他是否某时候会成为字块(然后我们也不用再管了)。
于是,我们就只须关心当前依然能炸到新节点x的块。不难发现,这些块之间两两有可以炸到的关系(即在图中,任意两个这样的块AB,要么A可达B,要么B可达A——不是相互可达)。它们的爆炸范围应该是依次包含的。
想到用栈来维护这些块(范围大的在栈底)。对于每个新节点,从栈顶块A判定是否A可炸到x,若否A出栈。直到栈顶的A能炸到,或者栈被清空。这个过程中,出栈的A除非是栈中最后一个元素,否则就一定成为“永远的子块”;若是最后一个元素,那么他就是“暂时的父块”,利用恰当的初始化可以立即判定他是否会在将来的某一时刻成为“永远的子块”。
然后,再从当前的栈顶块A开始判定,x是否可以炸到A。把所有可以的,都出栈,并与x合并成一个新的块,入栈。
可以这么做是因为,整个栈维护了下面的单调性:
(1) 自栈底向栈顶,块的爆炸范围(直接+间接)的右边界单调向左;
(2) 自栈底向栈顶,从右侧引爆该块的极限位置单调向右。
整个算法的时间复杂度。”

啊啊啊妙呆了
单调栈维护的块:
于是本蒟蒻照着大佬思路写了一发题解
恰当的初始化= youmin[i]表示i-n所有炸弹能炸到的最左位置,当pop栈底时看一下youmin[i]和栈底的right的关系即可
#include<iostream>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<stdio.h>
#include<stack>
using namespace std;
const int N=1000050;
int n;
struct boom 
{
	int x,d;
}hoof[N];
struct block
{
	int later,right;
};
stack<block> s;
int cmp(boom a,boom b)
{
	return a.x<b.x;
}
int ans1,ans2;
int youmin[N];
int cha(int rz)
{
	int l=1,r=n;
	while(l<r-1)
	{
		int mid=(l+r)/2;
		if(hoof[mid].x<=rz)
			l=mid+1;
		else
			r=mid;
	}
	if(hoof[l].x>rz)
		return l;
	else 
		return r;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)	
		scanf("%d%d",&hoof[i].x,&hoof[i].d);
	youmin[n]=hoof[n].x-hoof[n].d;
	for(int i=n-1;i>=1;i--)
		youmin[i]=min(youmin[i+1],hoof[i].x-hoof[i].d);
	sort(hoof+1,hoof+n+1,cmp); 
	for(int i=1;i<=n;i++)
	{
		while(!s.empty())
		{
			block now=s.top();
			if(now.right<hoof[i].x)
			{
				s.pop();
				if(!s.empty())
					ans1++;
				else
				{
					if(youmin[i]<=now.later)	
						ans1++;	
					else
						ans2++;	
				}
			}
			else
				break;
		}
		block zong;
		zong.later=hoof[i].x;
		zong.right=hoof[i].x+hoof[i].d;
		while(!s.empty())
		{
			block now=s.top();
			if(now.later>=hoof[i].x-hoof[i].d)
				s.pop(),zong.right=max(zong.right,now.right);
			else 
				break;
		}
		s.push(zong);
	}
	ans2++;
	ans1+=s.size()-1;
	printf("%d\n",ans2);
	printf("%d\n",ans2+ans1);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值