有一个包含n个元素的数组,要求实现以下操作:
DELETE k:删除位置k上的数。右边的数往左移一个位置。
QUERY i j:查询位置i~j上所有数的最小值和最大值。
例如有10个元素:
位置 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
元素 | 1 | 5 | 2 | 6 | 7 | 4 | 9 | 3 | 1 | 5 |
QUERY 2 8的结果为2 9。依次执行DELETE 3和DELETE 6(注意这时删除的是原始数组的元素7)后数组变为:
位置 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
元素 | 1 | 5 | 6 | 7 | 4 | 3 | 1 | 5 |
QUERY 2 8的结果为1 7。
【输入】
输入文件minmax.in第一行包含两个数n, m,表示原始数组的元素个数和操作的个数。第二行包括n个数,表示原始数组。以下m行,每行格式为1 k或者2 i j,其中第一个数为1表示删除操作,为2表示询问操作。
【输出】
输出文件minmax.out对每个询问操作输出一行,包括两个数,表示该范围内的最小值和最大值。
【样例输入】
10 4
1 5 2 6 7 4 9 3 1 5
2 2 8
1 3
1 6
2 2 8
【样例输出】
2 9
1 7
【限制】
50%的数据满足1<=n, m<=104,删除操作不超过100个
100%的数据满足1<=n, m<=106, 1<=m<=106
对于所有的数据,数组中的元素绝对值均不超过109
一道較難處理的線段樹。
線段樹對應的區間是靜態的,但出現題目中所述情況(即要不停地刪除節點),應該怎麼辦呢?
方法是將絕對位置變為相對位置!
用tree[p].Sum表示該節點中實際剩餘節點總數,那麼就可以很輕鬆地把相對位置維護出來了。
Accode:
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <bitset>
using std::min;
using std::max;
const char fi[] = "minmax.in";
const char fo[] = "minmax.out";
const int maxN = 1000010;
const int MAX = 0x3fffffff;
const int MIN = -MAX;
struct SegTree
{int L, R, lc, rc, Min, Max, Sum; };
SegTree tree[maxN << 1];
int a[maxN];
int n, N, tot;
void init_file()
{
freopen(fi, "r", stdin);
freopen(fo, "w", stdout);
}
void update(SegTree *ths, SegTree
*lc, SegTree *rc)
{
ths -> Sum = lc -> Sum + rc -> Sum;
ths -> Min = min(lc -> Min, rc -> Min);
ths -> Max = max(lc -> Max, rc -> Max);
}
void Build(int L, int R)
{
int Now = ++tot;
tree[Now].L = L;
tree[Now].R = R;
tree[Now].Sum = 1;
if (L == R)
{
tree[Now].Min = tree[Now].Max = a[L];
return;
}
if (L < R)
{
int Mid = (L + R) >> 1;
tree[Now].lc = tot + 1;
Build(L, Mid);
tree[Now].rc = tot + 1;
Build(Mid + 1, R);
}
update(tree + Now, tree + tree[Now].lc,
tree + tree[Now].rc);
} //建樹。
void Del(int Now, int p)
{
if (!(--tree[Now].Sum))
{
tree[Now].Max = MIN;
tree[Now].Min = MAX;
return;
} //將該區間的節點總數減1,
//若該區間的節點總數為0,
//那麼就將它的Min和Max賦為一個不可能的值。
if (p <= tree[tree[Now].lc].Sum)
Del(tree[Now].lc, p);
//p不超過左子樹的節點總數,則p在左子樹,
//繼續遍曆左子樹,相對位置不變。
else Del(tree[Now].rc,
p - tree[tree[Now].lc].Sum);
//否則p在右子樹,繼續遍曆右子樹,
//相對位置為p減去左子樹的節點數。
update(tree + Now, tree + tree[Now].lc,
tree + tree[Now].rc);
} //在此區間中,刪除第p個節點。
SegTree Query(int Now, int L, int R)
{
if (R - L + 1 == tree[Now].Sum)
return tree[Now];
//若實際總數剛好等於L到R包含的節點個數,
//則直接返回此節點的信息。
if (R <= tree[tree[Now].lc].Sum
&& tree[tree[Now].lc].Sum)
return Query(tree[Now].lc, L, R);
//若在左子樹且左子樹中有節點則返回左子樹的信息。
if (L > tree[tree[Now].lc].Sum
&& tree[tree[Now].rc].Sum)
return Query(tree[Now].rc,
L - tree[tree[Now].lc].Sum,
R - tree[tree[Now].lc].Sum);
//若在右子樹且右子樹中有節點則返回右子樹的信息,
//注意相對位置的改變。
SegTree ans, lc = Query(tree[Now].lc,
L, tree[tree[Now].lc].Sum),
rc = Query(tree[Now].rc, 1,
R - tree[tree[Now].lc].Sum);
//若跨左右兩棵子樹則二分繼續查找,
//注意此時相對位置與上不同。
update(&ans, &lc, &rc);
return ans;
} //在此區間中,查詢從第L個到第R個子區間的Min,Max值。
void work()
{
scanf("%d%d", &n, &N);
for (int i = 1; i < n + 1; ++i)
scanf("%d", a + i);
tot = 0;
Build(1, n);
for (; N; --N)
{
int p, x, y;
scanf("%d", &p);
switch (p)
{
case 1 :
{
scanf("%d", &x);
Del(1, x);
break;
}
case 2 :
{
scanf("%d%d", &x, &y);
if (x > y) std::swap(x, y);
SegTree tmp = Query(1, x, y);
printf("%d %d\n", tmp.Min,
tmp.Max);
break;
}
}
}
}
int main()
{
init_file();
work();
exit(0);
}
第二次做:
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <string>
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
const int maxN = 1000010;
const int MAX = 0x3f3f3f3f;
const int MIN = ~MAX;
struct SegTree {int Min, Max, sz;};
SegTree ans, tr[maxN << 1];
int a[maxN], lc[maxN << 1], rc[maxN << 1];
int n, m, tot;
inline void update(SegTree &ths,
const SegTree &lc,
const SegTree &rc)
{
ths.Min = min(lc.Min, rc.Min);
ths.Max = max(lc.Max, rc.Max);
ths.sz = lc.sz + rc.sz;
return;
}
void Build(int L, int R)
{
int Now = ++tot;
if (L == R)
{
tr[Now].Max = tr[Now].Min = a[L];
tr[Now].sz = 1;
return;
}
int Mid = (L + R) >> 1;
lc[Now] = tot + 1; Build(L, Mid);
rc[Now] = tot + 1; Build(Mid + 1, R);
update(tr[Now], tr[lc[Now]], tr[rc[Now]]);
return;
}
void Del(int p, int x)
{
if (!(--tr[p].sz))
{tr[p].Min = MAX; tr[p].Max = MIN; return;}
int Mid = tr[lc[p]].sz;
if (x <= Mid) Del(lc[p], x);
else Del(rc[p], x - Mid);
update(tr[p], tr[lc[p]], tr[rc[p]]);
return;
}
SegTree Query(int p, int L, int R)
{
if (R - L + 1 == tr[p].sz) return tr[p];
int Mid = tr[lc[p]].sz;
if (R <= Mid && tr[lc[p]].sz) //此时左子树必须存在。
return Query(lc[p], L, R);
if (Mid < L && tr[rc[p]].sz) //此时右子树必须存在。
return Query(rc[p], L - Mid, R - Mid);
SegTree ans, _lc = Query(lc[p], L, Mid),
_rc = Query(rc[p], 1, R - Mid);
update(ans, _lc, _rc);
return ans;
}
inline int getint()
{
int res = 0; char tmp; bool sgn = 1;
do tmp = getchar();
while (!isdigit(tmp) && tmp != '-');
if (tmp == '-') {sgn = 0; tmp = getchar();}
do res = (res << 3) + (res << 1) + tmp - '0';
while (isdigit(tmp = getchar()));
return sgn ? res : -res;
} //注意输出数据有负数。
int main()
{
freopen("minmax.in", "r", stdin);
freopen("minmax.out", "w", stdout);
n = getint(); m = getint();
for (int i = 1; i < n + 1; ++i)
a[i] = getint();
Build(1, n);
for (int x, y; m; --m)
switch (getint())
{
case 1: Del(1, getint()); break;
case 2:
x = getint(); y = getint();
if (x > y) std::swap(x, y);
//注意有x > y的情况。
ans = Query(1, x, y);
printf("%d %d\n", ans.Min, ans.Max);
break;
}
return 0;
}