Problem:acm.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;
}