目录
1008 Lawn of the Dead
题意:一个僵尸来到一块n×m的格子,最初在左上角的(1,1),只能向右或者向下走,在格子中有k个矿井,矿井处是不能到达的,问僵尸最多能到达多少个格子?
题解:将每一行可到达的点用线段树存为连续的区间,若上一行可到达范围为【L1,R1】,这一行可到达范围为【L2,R2】,若两者有交集,则更新当前行的区间,【max{L1,L2},R2】
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#define maxn 200005
using namespace std;
vector<int> v[maxn];
int sum[3][4*maxn], la[3][4*maxn];//开4倍数组
//线段树相关操作代码
void pushup(int id, int rt) {
sum[id][rt] = sum[id][rt << 1] + sum[id][rt << 1 | 1];
}
void pushdown(int id, int rt, int l, int r) {
if (la[id][rt] == -1) return;
int mid = (l + r) / 2;
sum[id][rt << 1] = (mid - l + 1) * la[id][rt];
sum[id][rt << 1 | 1] = (r - mid) * la[id][rt];
la[id][rt << 1] = la[id][rt];
la[id][rt << 1 | 1] = la[id][rt];
la[id][rt] = -1;
}
// id:树的编号(因为键两棵树) l:区间左端点 r:区间右端点 rt:结点编号
void build(int id, int l, int r, int rt) {
la[id][rt] = -1;
if (l == r) {
sum[id][rt] = 0;
return;
}
int mid = (l + r) / 2;
build(id, l, mid, rt << 1);
build(id, mid + 1, r, rt << 1 | 1);
pushup(id, rt);
}
//id:树的编号 L,R:更新区间的左右端点 val:更新的值 l,r:线段树的左右区间 rt:结点编号
void update(int id, int L, int R, int val, int l, int r, int rt) {
if (L <= l && R >= r) {
sum[id][rt] = (r - l + 1) * val;
la[id][rt] = (r - l + 1) * val;
return;
}
pushdown(id, rt, l, r);//懒人标记
int mid = (l + r) / 2;
if (L <= mid) update(id, L, R, val, l, mid, rt << 1);
if (R > mid) update(id, L, R, val, mid + 1, r, rt << 1 | 1);
pushup(id, rt);
}
//id:树的编号 LR:查询区间的左右端点 lr:线段树的左右端点 rt:结点编号
int query(int id, int L, int R, int l, int r, int rt) {
if (l == r) //两个地雷之间空了一格
{
if (sum[rt]) return l;
return -1;
}
pushdown(id, rt, l, r);
int mid = (l + r) / 2;
int pos = -1;
if (L <= mid && sum[id][rt << 1]) pos = query(id, L, R, l, mid, rt << 1);
if (pos != -1) return pos;
if (R > mid&& sum[id][rt << 1 | 1]) return query(id, L, R, mid + 1, r, rt << 1 | 1);
return -1;
}
int main()
{
int T;
scanf_s("%d", &T);
while (T--)
{
int n, m, k;
scanf_s("%d%d%d", &n, &m, &k);
long long int ans=0;
for (int i = 1; i <= n; i++)
v[i].clear();
for (int i = 1; i <= k; i++)
{
int x, y;
scanf_s("%d%d", &x, &y);
v[x].push_back(y);
}
for (int i = 1; i <= n; i++)
{
v[i].push_back(0);
v[i].push_back(m + 1);
sort(v[i].begin(), v[i].end());//将输入的地雷每行都按照列排序
}
int now = 1;
//建立两棵线段树来记录两行的数据
build(0, 1, m, 1);
build(1, 1, m, 1);
if (v[1].size()==2) //若第一行没有地雷(因为之前加了 0和m+1两个数 所以0个地雷时候值为2)
{
update(0, 1, m, 1, 1, m, 1);//将第一行的所有值设置为1
ans += m;
}
else
{
update(0, 1, v[1][1] - 1, 1, 1, m, 1);//将第一个地雷之前的位置都设置为1
ans += v[1][1] - 1;
}
for (int i = 2; i <= n; i++)
{
for (int j = 0; j < v[i].size() - 1; j++)
{
int l = v[i][j] + 1;//将一个地雷的右边一个位置设置为左
int r = v[i][j + 1] - 1;//将下一个地雷的左边一个位置设置为右
if (l > r)//两个地雷相邻的情况
continue;
//(l,r)为没有地雷的区间
int pos = query(now ^ 1, l, r, 1, m, 1);
if (pos == -1) continue;
update(now, pos, r, 1, 1, m, 1);//根据上一行的可到达位置,更新下一行可到达的位置
ans += r - pos+1;
}
now = now ^ 1;
update(now, 1, m, 0, 1, m, 1);
}
printf("%lld\n", ans);
}
}