题解 P3797 【妖梦斩木棒】

· · 题解

前面所有人都写了线段树题解,我来写个分块的。

跟线段树比起来,这题用分块会慢一点,但代码很好理解,在细节的处理上比线段树容易的多。所以建议学会这种解题思路,分块可以解决很多线段树很难解决的事。

把 n 个点分成 sqrt(n) 块,维护每块的完整木棒数量,是否有左右圆头。

用分块思想可以很直接很简单地修改或查询。

具体讲解在代码中。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define QWQ cout<<"QwQ"<<endl;
#include<vector>
#include<queue>
#include<stack>
#include<map>
using namespace std;
const int qwq=303030;
const int N=101010;
const int inf=0x3f3f3f3f;
//冗长的日常码前操作;
int n,m;
struct K {
    int l,r;         //左右边界;
    bool zuo,you;    //是否有左右圆头木棒;
    int sum;         //块中整个的木棒数;
} k[N];
char a[qwq];         //记录每一块的状态;
char cz[3];          //用字符串读入,就避免读入空行或括号了;
int t;

inline K search(int x,int y) { //访问块中信息;
    K res = (K) { x,y,0,0,0 };
    char now = 0;   //now记录木棍状态;
    for(int i=x; i<=y; i++) 
    {
        if( a[i]=='(' )
        now = '(';
        if( a[i]==')' ) 
        {
            if(now== 0 ) res.zuo = 1; //判断向左圆头;
            if(now=='(') res.sum++; //完整块;
            now = ')';
        }
    }
    if(now=='(')
    res.you = 1;   //判断向右圆头; 
    return res;    //返回块中信息;
}

inline void change(int x,char z) {
    a[x] = z;
    for(int i=1; i<=t; i++)
    if(x>=k[i].l && x<=k[i].r) //找到x所属块;
    { 
        k[i] = search( k[i].l,k[i].r ); //修改块中信息;
        return ;
    }
}

inline int query(int x,int y) {
    int ans = 0;
    bool yuan = 0;     //是否有向右的圆头;
    for(int i=1; i<=t; i++) 
    {
        if(k[i].l<=x && y<=k[i].r)  //整个在块中;
        {
            K res = search( x,y );
            return res.sum;
        } 
        else if(x>=k[i].l && x<=k[i].r) //左端在块中;
        { 
            K res = search( x,k[i].r );
            ans += res.sum;
            yuan = res.you;
        } 
        else if(x<k[i].l && k[i].r<y) //块在询问区间中;
        { 
            ans += k[i].sum;
            if( k[i].zuo && yuan ) ans++; //左右对上;
            if( (!k[i].sum) && (!k[i].you) && (!k[i].zuo) ) continue;
            //注意这里有一个陷阱:块中全为X(空块)。
            //如果你想记录是否有向右圆头,不能用这个空块来修改;
            //可以证明:一个块当它既无完整木棒又无向左向右圆头时为空块。 
            yuan = k[i].you;
        } 
        else if(y>=k[i].l && y<=k[i].r) //右端在块中;
        { 
            K res = search( k[i].l,y);
            ans += res.sum;
            if( res.zuo && yuan) ans++;
            return ans;
        }
    }
    return ans;
}

int main() {
    int x,y;
    scanf("%d%d",&n,&m);
    //开始建造分块;
    t = sqrt(n);
    for(int i=1; i<=t; i++) {
        k[i].l = k[i-1].r + 1;
        k[i].r = i * t;
    }
    if(k[t].r != n) {
        t++;
        k[t].l = k[t-1].r + 1;
        k[t].r = n;
    }
    a[1] = '('; k[1].you = 1;
    a[n] = ')'; k[t].zuo = 1;
    //左右一改,建造完成; 
    while( m-- ) {
        scanf("%d",&x);
        if(x==1) {
            scanf("%d%s",&x,cz);
            change( x,cz[0] );
        } else {
            scanf("%d%d",&x,&y);
            printf("%d\n", query(x,y) );
        }
    }
    return 0;
}

代码看来会有点长,也可能是我写的有点稀,压压行就显得短了;

其实也挺好写的对吧,只要注意到一些细节。