HDU 1540 - Tunnel Warfare(线段树区间合并)

Tunnel Warfare

TimeLimit: 4000/2000 MS (Java/Others)    Memory Limit:65536/32768 K (Java/Others)
Total Submission(s): 10244    Accepted Submission(s): 4023

ProblemDescription

During the War of Resistance AgainstJapan, tunnel warfare was carried out extensively in the vast areas of northChina Plain. Generally speaking, villages connected by tunnels lay in a line.Except the two at the ends, every village was directly connected with twoneighboring ones.

Frequently the invaders launched attack on some of the villages and destroyedthe parts of tunnels in them. The Eighth Route Army commanders requested thelatest connection state of the tunnels and villages. If some villages areseverely isolated, restoration of connection must be done immediately!

 

 

Input

The first line of the input contains twopositive integers n and m (n, m ≤ 50,000) indicating the number of villages andevents. Each of the next m lines describes an event.

There are three different events described in different format shown below:

D x: The x-th village was destroyed.

Q x: The Army commands requested the number of villages that x-th village wasdirectly or indirectly connected with including itself.

R: The village destroyed last was rebuilt.

 

 

Output

Output the answer to each of the Armycommanders’ request in order on a separate line.

 

 

SampleInput

7 9

D 3

D 6

D 5

Q 4

Q 5

R

Q 4

R

Q 4

 

 

SampleOutput

1

0

2

4

 

【题意】

        N个村庄连成一条直线,相邻的两个村庄有一条道路相连,现在有M次操作,分以下3种。(1)D x 表示摧毁第x个村庄以及和它相连的道路。(2)Q x 表示查询第x个村庄和多少个村庄相连接(包括第x个村庄自身),如果第x个村庄被毁则为0。(3)R 重建最后被毁的村庄和相应道路。对于每次的查询操作输出相应结果。

 

【思路一】

        利用线段树最基本的用法求最大值和最小值来求解,想法比较巧妙。对于村庄的区间[1,N],对其建立线段树,这个线段树中要维护两个值,分别是当前区间中被毁村庄编号的最小值和最大值minv,maxv,但是要注意对于单点x来说,如果该存庄没有被毁则minv=0,maxv=N+1便于计算,否则minv=maxv=x,大体思路就是找到当前村庄左边被毁村庄的最大值和右边被毁村庄的最小值。举个简单的例子:

minv

8

8

3

8

8

6

8

maxv 

0

0

3

0

0

6

0

村庄

1          

2          

3(被毁)

4          

5          

6(被毁 )

7         


       查询被毁的村庄:如果要查询第3个村庄,[1,3]中被毁村庄maxv的最大值l=3和[3,7]中被毁村庄minv的最小值r=3,即l==r时输出0即可       查询没有被毁的村庄:如果要查询第4个村庄,那么只需要找到[1,4]中被毁村庄maxv的最大值l=3和[4,7]中被毁村庄minv的最小值r=6,那么l-r-1=2便是答案;如果要查询第2个村庄,同理[1,2]中被毁村庄maxv的最大值l=0,[2,7]中被毁村庄minv的最小值r=3,r-l-1=2便是答案,这里也解释了为什么要把没有被毁的村庄的minv置为N+1,maxv置为0。

       综上可以根据线段树的两次查询得到一组答案

if (l == r) ans = 0;

else ans = r– l – 1;

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

const int maxn = 50500;

#define node tree[id]
#define lson tree[id*2]
#define rson tree[id*2+1]

int n, m;
struct Tree {
	int left, right, minv, maxv;
}tree[maxn * 4];

void pushup(int id) {
	node.maxv = max(lson.maxv, rson.maxv);
	node.minv = min(lson.minv, rson.minv);
}

void build(int id, int le, int ri) {
	node.left = le;
	node.right = ri;
	node.maxv = 0;
	node.minv = n + 1;
	if (le == ri) return;
	int mid = (le + ri) >> 1;
	build(id * 2, le, mid);
	build(id * 2 + 1, mid + 1, ri);
}

int query_min(int id, int x, int y) {//查询区间内minv的最小值
	if (node.left >= x && node.right <= y) {
		return node.minv;
	}
	pushup(id);
	int ans = n + 1;
	int mid = (node.left + node.right) >> 1;
	if (x <= mid) ans = min(ans, query_min(id * 2, x, y));
	if (y > mid) ans = min(ans, query_min(id * 2 + 1, x, y));
	return ans;
}

int query_max(int id, int x, int y) {//查询区间内maxv的最大值
	if (node.left >= x && node.right <= y) {
		return node.maxv;
	}
	pushup(id);
	int ans = 0;
	int mid = (node.left + node.right) >> 1;
	if (x <= mid) ans = max(ans, query_max(id * 2, x, y));
	if (y > mid) ans = max(ans, query_max(id * 2 + 1, x, y));
	return ans;
}

void update(int id, int pos, int v) {//更新区间,v==-1代表重建
	if (node.left == node.right) {
		if (-1 != v) node.maxv = node.minv = v;
		else {
			node.maxv = 0;
			node.minv = n + 1;
		}
		return;
	}
	int mid = (node.left + node.right) >> 1;
	if (pos <= mid) update(id * 2, pos, v);
	else update(id * 2 + 1, pos, v);
	pushup(id);
}

int main() {
	char op;
	int x;
	while (scanf("%d%d", &n, &m) == 2) {
		stack<int> st;//记录被毁顺序
		build(1, 1, n);
		while (m--) {
			scanf(" %c", &op);
			if ('D' == op) {
				scanf("%d", &x);
				update(1, x, x);
				st.push(x);
			}
			else if ('Q' == op) {
				scanf("%d", &x);
				int ans;
				int l = query_max(1, 1, x);
				int r = query_min(1, x, n);
				if (l == r) ans = 0;
				else ans = r - l - 1;
				printf("%d\n", ans);
			}
			else {
				int tmp = st.top();
				st.pop();
				update(1, tmp, -1);
			}
		}
	}
	return 0;
}

【思路二】

        把村庄抽象成点,1表示未摧毁,0表示已摧毁。线段树中要维护3个值,ls,rs,ms。ls表示从该结点对应的区间最左端开始往右的连续区间长度,同理rs表示从对应区间最右端开始往左的连续区间长度,ms表示当前结点对应区间的最长连续区间长度(全是1)。

        通过递推即可求解,具体递推关系看代码,这就是线段树的区间合并问题。

update(int id, int pos, int v)用于破坏或修复第pos个村庄。

query(int id, int pos)用于查询以id为根结点对应区间中,包含v的最大连续区间长度。

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

const int maxn = 50500;

#define node tree[id]
#define lson tree[id*2]
#define rson tree[id*2+1]

int n, m;
struct Tree {
	int left, right, ls, rs, ms;
}tree[maxn << 2];

void build(int id, int le, int ri) {
	node.left = le;
	node.right = ri;
	node.ls = node.rs = node.ms = ri - le + 1;
	if (le == ri) return;
	int mid = (le + ri) >> 1;
	build(id * 2, le, mid);
	build(id * 2 + 1, mid + 1, ri);
}

void update(int id, int pos, int v) {
	//破坏或修复第pos个村庄
	if (node.left == node.right) {
		if (0 == v) node.ls = node.rs = node.ms = 0; //v==0破坏
		else node.ls = node.rs = node.ms = 1; //v==1修复
		return;
	}
	int mid = (node.left + node.right) >> 1;
	if (pos <= mid) update(id * 2, pos, v);
	else update(id * 2 + 1, pos, v);

	node.ls = lson.ls;//当前结点的左区间
	node.rs = rson.rs;//当前结点的右区间
	node.ms = max(lson.rs + rson.ls, max(lson.ms, rson.ms));
	//当前结点对应的最大连续区间一定是
	//左结点的最大连续区间,右结点最大连续区间,左结点右区间+右结点左区间 这三者的最大值
	if (lson.ls == lson.right - lson.left + 1) node.ls += rson.ls;
	//左结点的左区间满,那么当前结点的左区间还要再加上右结点的左区间
	if (rson.rs == rson.right - rson.left + 1) node.rs += lson.rs;
	//当前结点的右区间同理
}

int query(int id, int pos) {
	if (node.left == node.right || 0 == node.ms || node.right - node.left + 1 == node.ms) {
		//递归到单点或者 对应的区间为满或空就可以直接返回了
		return node.ms;
	}
	int ans;
	int mid = (node.left + node.right) >> 1;
	if (pos <= mid) {
		if (pos >= lson.right - lson.rs + 1) ans = query(id * 2, pos) + query(id * 2 + 1, mid + 1);
		//如果pos刚好处在左结点最右端的连续区间上面那么就还要再加上右结点对应最左端的连续区间
		else ans = query(id * 2, pos);
		//否则只要看左结点就可以了
	}
	else {
		//右结点同理
		if (pos <= rson.left + rson.ls - 1) ans = query(id * 2 + 1, pos) + query(id * 2, mid);
		else ans = query(id * 2 + 1, pos);
	}
	return ans;
}

int main() {
	char op;
	int x;
	while (scanf("%d%d", &n, &m) == 2) {
		stack<int> st;
		build(1, 1, n);
		while (m--) {
			scanf(" %c", &op);
			if ('D' == op) {
				scanf("%d", &x);
				update(1, x, 0);
				st.push(x);
			}
			else if ('Q' == op) {
				scanf("%d", &x);
				int ans = query(1, x);
				printf("%d\n", ans);
			}
			else {
				int tmp = st.top();
				st.pop();
				update(1, tmp, 1);
			}
		}
	}
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值