Lawn of the Dead (线段树)

传送门

给定一个n*m的网格,有k个地雷,不能踩到地雷所在格子,从(1,1)出发,只能往左或者往下走,问最多可以到达多少个格子。

由于n,m都是1e5级别,所以我们不能模拟。

我们先观察一个结论,

  • 对于第i行,假设有y1,y2两个雷,[y1+1,y2-1]这个区间格子如何能被到达呢?

  • 只能从上面下来,所以我们在第i-1行中找到[y1+1,y2-1]这个区间内最左端的可到达的位置idx,那么我们从第i-1行的idx列走下来到达第i行的idx列,所以第i行的[idx,y2-1]这个区间都是可到达的了。

  • 如果我们用1代表可到达,0代表不可到达,上面的操作实质就是区间查询,区间覆盖,最后每行的答案就是查询区间和,每行答案累加起来就是最终答案。(除了维护区间和之外,还需要额外维护区间内最左端1的位置)

  • 我们考虑对每行都建一个线段树,但是空间不够,我们可以只建两棵,用滚动数组的思想。每次滚动都把区间更新成全是0的情况。

  • 枚举到第i行时,每次先假设第i行都是0 ([1,m]的区间覆盖),然后枚举地雷进行区间更新

  • 细节:1、第一行需要特判。2、我们可以假设第0列,第m+1列都是雷,这样可以省略很多特殊情况的判断

#include<bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define ull unsigned long long
#define ll long long
#define pii pair<int, int>
const int maxn = 1e5 + 10;
const ll mod = 998244353;
const ll inf = (ll)4e16+5;
const int INF = 1e9 + 7;
const double pi = acos(-1.0);
ll inv(ll b){
    
    if(b==1)return 1;return(mod-mod/b)*inv(mod%b)%mod;}
inline ll read()
{
    
    
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){
    
    if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){
    
    x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
struct node 
{
    
    
    #define lc rt<<1
    #define rc rt<<1|1
    int l,r;
    int idx;//最小的 1所在的列 没有1则记为m+1 1表示可以走
    int sum;//区间和  表示可以走的格子数 每结束一行 求一次区间和 累加答案 
}tree[2][maxn<<2];
int tag[2][maxn<<2];//区间覆盖的lazy 1表示覆盖成1  2表示覆盖成0
int n,m,q;
inline void pushup(int rt,int k) 
{
    
    
    tree[k][rt].sum=tree[k][lc].sum + tree[k][rc].sum;
    tree[k][rt].idx=min(tree[k][lc].idx , tree[k][rc].idx);
}
void build(int rt,int l,int r,int k)//刚开始假设全部不能走
{
    
    
    tree[k][rt].l=l,tree[k][rt].r=r;
    tag[k][rt]=0;
    if(l==r)
    {
    
    
        tree[k][rt].idx=m+1;
        tree[k][rt].sum=0;
        return ;
    }
    int mid=l+r>>1;
    build(lc,l,mid,k);build(rc,mid+1,r,k);
    pushup(rt,k);
}
inline void change(int rt,int v,int k)
{
    
    
    tree[k][rt].sum=(tree[k][rt].r-tree[k][rt].l+1)*(v%2);
    if(!tree[k][rt].sum) 
    {
    
    
        tree[k][rt].idx=m+1;
    }
    else tree[k][rt].idx=tree[k][rt].l;
    tag[k][rt]=v;
}
inline void pushdown(int rt,int k)
{
    
    
    if(tag[k][rt]) 
    {
    
    
        change(lc,tag[k][rt],k);
        change(rc,tag[k][rt],k);
        tag[k][rt]=0;
    }
}
void upd(int rt,int vl,int vr,int v,int k)
{
    
    
    int l=tree[k][rt].l,r=tree[k][rt].r;
    if(vr<l || vl>r || vr<vl) return ;
    if(vl<=l && r<=vr) 
    {
    
    
        change(rt,v,k);
        return ;
    }
    pushdown(rt,k);
    upd(lc,vl,vr,v,k);
    upd(rc,vl,vr,v,k);
    pushup(rt,k);
}
int qry(int rt,int vl,int vr,int k)
{
    
    
    int l=tree[k][rt].l,r=tree[k][rt].r;
    if(vr<l || vl>r || vr<vl) return m+1;
    if(vl<=l && r<=vr) 
    {
    
    
        return tree[k][rt].idx;
    }
    pushdown(rt,k);
    return min(qry(lc,vl,vr,k) , qry(rc,vl,vr,k));
}
int qry1(int rt,int vl,int vr,int k)
{
    
    
    int l=tree[k][rt].l,r=tree[k][rt].r;
    if(vr<l || vl>r || vr<vl) return 0;
    if(vl<=l && r<=vr) 
    {
    
    
        return tree[k][rt].sum;
    }
    pushdown(rt,k);
    return qry(lc,vl,vr,k) + qry(rc,vl,vr,k);
}
vector<int> dead[maxn];
int main()
{
    
    
    int t;cin>>t;
    while(t--)
    {
    
    
        scanf("%d %d %d",&n,&m,&q);
        for(int i=0;i<=n+1;i++) dead[i].clear();
        for(int i=1,x,y;i<=q;i++)
        {
    
    
            scanf("%d %d",&x,&y);
            dead[x].push_back(y);
        }
        //不妨假设第0列 m+1列 全是雷
        for(int i=1;i<=n;i++) 
        {
    
    
            dead[i].push_back(0);
            dead[i].push_back(m+1);
            sort(dead[i].begin(),dead[i].end());
        }

        build(1,1,m,1);//建树
        build(1,1,m,0);
        ll ans=0;
        //特判第一行
        if((int)dead[1].size() == 2) //除0 m+1列外没有雷
        {
    
    
            upd(1,1,m,1,1);
        }
        else upd(1,1,dead[1][1]-1,1,1);//1到第一个雷前一格的这段区间 可以走
        ans+=qry1(1,1,m,1);

        for(int i=2;i<=n;i++)
        {
    
    
            upd(1,1,m,2,i%2);//滚动数组 先假设这一行全不能走
            for(int j=0;j<(int)dead[i].size()-1;j++)
            {
    
    
                int y1=dead[i][j],y2=dead[i][j+1];
                //第i-1行的线段树中 查询[y1+1,y2-1]区间内 最左端的1
                int left=qry(1,y1+1,y2-1,(i-1)%2);
                if(left == m+1) continue;//都是0
                upd(1,left,y2-1,1,i%2);
            }
            ans+=qry1(1,1,m,i%2);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_46030630/article/details/121181951