hdu 1540 Tunnel Warfare

31 篇文章 0 订阅
19 篇文章 0 订阅

Problemacm.hdu.edu.cn/showproblem.php?pid=1540

题意

n个村庄连成一条线,有3种操作:

D x:摧毁编号为 x 的村庄

Q x:询问与编号为 x 的村庄相连的村庄的数量(包括 x 自身)

R:修复最近一次被摧毁的那个村庄(恢复与相邻村庄的连接)

分析

个人认为本题最大的坑点是没告诉你有多组输入…

一种思路是:用 set 和 stack 记录已被摧毁的村庄编号。

查询时,先找 set 中是否存在 x,如果有,直接是 0;如果没有,找到在 x 左右两边离它最近的已摧毁村庄,用这两个编号算出中间连续的村庄数量。

stack 是用在修复时,找最近一次摧毁的村庄编号,并将它从 set 中删除。

这种做法要注意把 0 号和 n+1 号预先插入到 set 中;记得每次清空 set 和 stack。

#include <cstdio>
#include <set>
#include <stack>
using namespace std;
const int N = 50000;

set<int> des;
stack<int> stk;

int main()
{
	for(int n,m; ~scanf("%d%d", &n, &m); )
	{
		des.insert(0);
		des.insert(n+1);
		while(m--)
		{
			char op;
			int x;
			scanf(" %c", &op);
			if(op == 'D')
			{
				scanf("%d", &x);
				des.insert(x);
				stk.push(x);
			}
			else if(op == 'Q')
			{
				scanf("%d", &x);
				int a = *--des.lower_bound(x),
					b = *des.lower_bound(x);
				printf("%d\n", b==x ? 0 : b-a-1);
			}
			else if(!stk.empty())
			{
				x = stk.top();
				stk.pop();
				des.erase(x);
			}
		}
		des.clear();
		while(!stk.empty())
			stk.pop();
	}
	return 0;
}


另一种是线段树区间合并。

线段树记录 3 个值:该区间最大的连续村庄数、从左端点开始的连续村庄数、从右端点开始的连续村庄数。

(一个区间不是只有左、右两端有连续村庄,只是只记录这 3 个数据)


最大的问题在于中间的一段连续区间被分开在两个结点中,主要是想办法将这两段本来是连在一起的连起来。


由于记录了每个区间左、右端开始的连续村庄数,那么在每个结点上,都可以结合端点编号和这两个长度,判断一个村庄是否在此区间最左边(最右边)的连续村庄中,若在,则要合并上左边(右边)那个区间的最右边(最左边)的连续村庄。

(为了容易看清,代码中用两个宏 left(x) 和 right(x),刻意地突出左、右儿子)

#include <cstdio>
#include <stack>
#include <algorithm>
using namespace std;
#define left(x) (x << 1)
#define right(x) (x << 1 | 1)
const int N = 50000;

int le[N+7<<2]; // left end,左端点开始的连续村庄
int re[N+7<<2]; // right end,右端点……
int tree[N+7<<2]; // 该区间最长的连续村庄
stack<int> des;

void pushup(int len, int x)
{
	le[x] = le[left(x)];
	re[x] = re[right(x)];

	// 左端连续村庄数 与 左子区间总村庄数 等大
	// 意味着整个左子区间都是连续的
	// 就要加上 右子区间的左端连续村庄数
	if(le[x] == len - (len >> 1))
		le[x] += le[right(x)];
	// 右子区间与左子区间类似
	if(re[x] == len >> 1)
		re[x] += re[left(x)];

	tree[x] = max(re[left(x)] + le[right(x)],
			max(tree[left(x)], tree[right(x)]));
}

void build(int l, int r, int id)
{
	tree[id] = le[id] = re[id] = r - l + 1;
	if(l == r) return;
	int m = l + r >> 1;
	build(l, m, left(id));
	build(m+1, r, right(id));
}

void update(int p, int v, int l, int r, int id)
{
	if(l == r)
	{
		tree[id] = le[id] = re[id] = v;
		return;
	}
	int m = l + r >> 1;
	if(m < p)
		update(p, v, m+1, r, right(id));
	else
		update(p, v, l, m, left(id));
	pushup(r-l+1, id);
}

int query(int p, int l, int r, int id)
{
	// 叶子结点、区间满、区间空 都是直接返回
	if(l == r || tree[id] == r-l+1 || !tree[id])
		return tree[id];

	int m = l + r >> 1;
	if(m < p) // 在右子区间
		if(p < m+1 + le[right(id)]) // 在右子区间的左端开始的连续村庄中
			return query(p, m+1, r, right(id)) + re[left(id)];
		else // 不在左端开始的连续村庄中
			return query(p, m+1, r, right(id));
	else // 在左子区间
		if(p > m - re[left(id)]) // 在左子区间的右端开始的连续区间中
			return query(p, l, m, left(id)) + le[right(id)];
		else // 不在右端开始的连续区间中
			return query(p, l, m, left(id));
}

int main()
{
	for(int n,m; ~scanf("%d%d", &n, &m); )
	{
		build(1, n, 1);
		while(m--)
		{
			char op;
			int x;
			scanf(" %c", &op);
			if(op == 'D')
			{
				scanf("%d", &x);
				update(x, 0, 1, n, 1);
				des.push(x);
			}
			else if(op == 'Q')
			{
				scanf("%d", &x);
				printf("%d\n", query(x, 1, n, 1));
			}
			else if(!des.empty())
			{
				x = des.top();
				des.pop();
				update(x, 1, 1, n, 1);
			}
		}
		while(!des.empty())
			des.pop();
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值