这道题又做得颇有感触...线段树好像总是很灵活,不过我觉得它应该也是有章可循的
这次用自己配的gvim写,感觉还不错
这次还学到一点技巧,就是无限WA时可以写个暴力程序对拍,对于这道题虽然没法一步一步跟踪数据,但是这样的对拍还是给了我许多启发
线段树应该记录cover表示当前节点是否覆盖,以及覆盖的类型
然后还是三部曲:进入子节点之前分配信息,递归调用子节点,从子节点回来合并信息
/*
* Author: rush
* Created Time: 2010年05月01日 星期六 15时17分10秒
* File Name: icpc/hdu/3397.cpp
*/
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using std::swap;
using std::max;
int max3(int a, int b, int c)
{ return max(a, max(b, c)); }
int max4(int a, int b, int c, int d)
{ return max(a, max3(b, c, d)); }
const int size = 100000 + 5;
struct node_t {
int L0, M0, R0;
int L1, M1, R1;
int ONE;
int range;
int cover; // 覆盖情况(-1是未覆盖,0, 1, 2如题)
void reset(const int _r)
{
range = _r;
L0 = M0 = R0 = range;
L1 = M1 = R1 = 0;
ONE = 0;
}
void perform(int op)
{
switch (op) {
case 0:
L0 = M0 = R0 = range;
L1 = M1 = R1 = 0;
ONE = 0;
cover = 0;
break;
case 1:
L0 = M0 = R0 = 0;
L1 = M1 = R1 = range;
ONE = range;
cover = 1;
break;
case 2:
swap(L0, L1), swap(M0, M1), swap(R0, R1);
ONE = range - ONE;
// 这里很关键,根据之前的覆盖情况确定现在的覆盖情况,
// 其实对于case 0和case 1也有这个问题,不过因为是直接改变最终值为0或1,
// 而不是像case 2这样根据之前情况改变,所以那里没有这样分情况处理
switch (cover) {
case -1:
cover = 2; break;
case 0:
cover = 1; break;
case 1:
cover = 0; break;
case 2:
cover = -1; break;
}
break;
}
}
} tree[size * 3];
int n, m;
void plant(int low, int high, int node)
{
tree[node].reset(high - low + 1);
tree[node].cover = -1;
int mid = (low + high) / 2;
// low < high 恰好能够处理[x, x]的区间(即对于单个点构成的区间)
if (low < high)
{
plant(low, mid, node * 2);
plant(mid + 1, high, node * 2 + 1);
}
}
int left, right;
void update(int low, int high, int node, int op)
{
if (left <= low && high <= right) {
tree[node].perform(op);
} else if (left <= high && low <= right) {
int mid = (low + high) / 2;
// 进入递归前,如果当前结点被覆盖,则将其属性传递给子节点
if (tree[node].cover != -1)
{
tree[node * 2].perform(tree[node].cover);
tree[node * 2 + 1].perform(tree[node].cover);
tree[node].cover = -1;
}
// 递归调用左右子节点,这里不用判断low < high是因为如果有单点区间,
// 则单点一定会被本函数的第一个if考虑到
update(low, mid, node * 2, op);
update(mid + 1, high, node * 2 + 1, op);
// 从左右子节点回来,更新父节点的信息
node_t &pnt = tree[node], &lch = tree[node * 2], &rch = tree[node * 2 + 1];
pnt.L0 = lch.L0 + (lch.L0 == lch.range ? rch.L0 : 0);
pnt.M0 = max3(lch.M0, lch.R0 + rch.L0, rch.M0);
pnt.R0 = rch.R0 + (rch.R0 == rch.range ? lch.R0 : 0);
pnt.L1 = lch.L1 + (lch.L1 == lch.range ? rch.L1 : 0);
pnt.M1 = max3(lch.M1, lch.R1 + rch.L1, rch.M1);
pnt.R1 = rch.R1 + (rch.R1 == rch.range ? lch.R1 : 0);
pnt.ONE = lch.ONE + rch.ONE;
}
}
// 其实只需要记录询问的区间被拆成哪2 * log n个子区间就行了,
// 所以不按常规的做法,而是像我这样把它们标记出来也是一样的
int a[32 * 2], la;
void query(int low, int high, int node)
{
if (left <= low && high <= right) {
a[la++] = node;
} else if (left <= high && low <= right) {
int mid = (low + high) / 2;
// 查询时也需要将父节点信息传递给子节点,
// 这是因为前面update函数只能保证从2*logn个子区间往上的信息是对的
// 往下的没有做更新(如果做了就是指数的复杂度了)
if (tree[node].cover != -1)
{
tree[node * 2].perform(tree[node].cover);
tree[node * 2 + 1].perform(tree[node].cover);
tree[node].cover = -1;
}
query(low, mid, node * 2);
query(mid + 1, high, node * 2 + 1);
}
}
int main()
{
freopen("data.in", "r", stdin);
freopen("my.out", "w", stdout);
int T;
scanf("%d", &T);
while (T--)
{
scanf("%d%d", &n, &m);
plant(0, n - 1, 1);
int t;
for (int i = 0; i < n; ++i)
{
scanf("%d", &t);
left = right = i;
update(0, n - 1, 1, t);
}
for (int i = 0; i < m; ++i)
{
scanf("%d%d%d", &t, &left, &right);
if (t < 3) {
update(0, n - 1, 1, t);
} else {
la = 0;
query(0, n - 1, 1);
if (t == 3) {
int ans3 = 0;
for (int j = 0; j < la; ++j)
ans3 += tree[a[j]].ONE;
printf("%d\n", ans3);
} else {
int ans4 = 0;
for (int j = 0; j < la; ++j)
ans4 = max(ans4, tree[a[j]].M1);
int tmp = 0;
for (int j = 0; j < la; ++j)
if (tree[a[j]].ONE == tree[a[j]].range) {
tmp += tree[a[j]].range;
} else {
tmp += tree[a[j]].L1;
ans4 = max(ans4, tmp);
tmp = tree[a[j]].R1;
}
ans4 = max(ans4, tmp);
printf("%d\n", ans4);
}
}
}
}
return 0;
}