码迷,mamicode.com
首页 > 编程语言 > 详细

BZOJ4785 [Zjoi2017]树状数组 【二维线段树 + 标记永久化】

时间:2018-05-16 22:36:34      阅读:212      评论:0      收藏:0      [点我收藏+]

标签:cst   链接   stdout   大小   out   题解   改变   flag   map   

题目链接

BZOJ4785

题解

肝了一个下午QAQ没写过二维线段树还是很难受

首先题目中的树状数组实际维护的是后缀和,这一点凭分析或经验或手模观察可以得出
\(\mod 2\)意义下,我们实际求出的区间和是\([l - 1,r - 1]\),和\([l,r]\)唯一不同的就在于\(l - 1\)\(r\)
所以每个询问实际是询问两个位置值相同的概率

我们把询问看做二元组\((a,b)\),其中\(a \le b\),我们要维护\((a,b)\)不同的概率【至于为什么是不同而不是相同,等下说】
初始概率都为\(0\)
对于修改操作\([l,r]\)
\(a \in [1,l - 1],b \in [l,r]\)时,此时有\(\frac{1}{len}\)的概率改变不等关系
\(a \in [l,r],b \in [r + 1,n]\)时,此时有\(\frac{1}{len}\)的概率改变不等关系
\(a,b \in [l,r]\)时,此时有\(\frac{2}{len}\)的概率改变不等关系

所以我们可以使用二维线段树维护这些区域的概率值
如果原不等概率为\(p_0\),现在有\(p_1\)的概率改变不等关系
那么新的概率\(p‘ = p_0(1 - p_1) + (1 - p_0)p_1 = p_0 + p_1 - 2p_0p_1\)
我们记其为概率的合并
经计算可以得出,这样的合并满足交换律结合律单位元\(0\)
所以我们可以使用线段树很方便地维护
同时为了简化操作,我们采用标记永久化
现在就可以理解我们为什么要维护不等概率了,因为初值为\(0\),恰好也为合并运算的单位元

二维线段树标记永久化的姿势:
内层线段树将标记储存在路径上的节点中
外层线段树则通过在修改中途访问内层线段树而就此停止,在询问时询问路径上所有的内层线段树即可

还要注意空间大小,\(O(nlog^2n)\)要开足够大的空间

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<map>
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define mp(a,b) make_pair<int,int>(a,b)
#define cls(s) memset(s,0,sizeof(s))
#define cp pair<int,int>
#define LL long long int
using namespace std;
const int maxn = 500005,maxm = 40000005,INF = 1000000000,P = 998244353;
inline int read(){
    int out = 0,flag = 1; char c = getchar();
    while (c < 48 || c > 57){if (c == ‘-‘) flag = -1; c = getchar();}
    while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();}
    return out * flag;
}
int qpow(int a,int b){
    int re = 1;
    for (; b; b >>= 1,a = 1ll * a * a % P)
        if (b & 1) re = 1ll * re * a % P;
    return re;
}
int inv(int x){return qpow(x,P - 2);}
int merge(int x,int y){
    return (((x + y) % P - 2ll * x * y % P) + P) % P;
}
int n,m,ls[maxm],rs[maxm],val[maxm],rt[maxn << 2],cnt,ans;
void modify(int& u,int l,int r,int L,int R,int v){
    if (!u) u = ++cnt;
    if (l >= L && r <= R){
        val[u] = merge(val[u],v);
        return;
    }
    int mid = l + r >> 1;
    if (mid >= L) modify(ls[u],l,mid,L,R,v);
    if (mid < R) modify(rs[u],mid + 1,r,L,R,v);
}
void query(int u,int l,int r,int pos){
    if (!u) return; ans = merge(ans,val[u]);
    if (l == r) return;
    int mid = l + r >> 1;
    if (mid >= pos) query(ls[u],l,mid,pos);
    else query(rs[u],mid + 1,r,pos);
}
void Modify(int u,int l,int r,int L,int R,int ll,int rr,int v){
    if (l >= L && r <= R){
        modify(rt[u],1,n,ll,rr,v);
        return;
    }
    int mid = l + r >> 1;
    if (mid >= L) Modify(u << 1,l,mid,L,R,ll,rr,v);
    if (mid < R) Modify(u << 1 | 1,mid + 1,r,L,R,ll,rr,v);
}
void Query(int u,int l,int r,int Pos,int pos){
    if (rt[u]) query(rt[u],1,n,pos);
    if (l == r) return;
    int mid = l + r >> 1;
    if (mid >= Pos) Query(u << 1,l,mid,Pos,pos);
    else Query(u << 1 | 1,mid + 1,r,Pos,pos);
}
int main(){
    //freopen("in.in","r",stdin);
    //freopen("out1.txt","w",stdout);
    n = read(); m = read();
    int opt,l,r,len,p;
    while (m--){
        opt = read(); l = read(); r = read();
        if (opt & 1){
            len = r - l + 1; p = inv(len);
            if (l > 1){
                Modify(1,1,n,1,l - 1,l,r,p);
                modify(rt[0],1,n,1,l - 1,1);
            }
            if (r < n){
                Modify(1,1,n,l,r,r + 1,n,p);
                modify(rt[0],1,n,r + 1,n,1);
            }
            Modify(1,1,n,l,r,l,r,2 * p % P);
            modify(rt[0],1,n,l,r,(1 - p + P) % P);
        }
        else {
            ans = 0;
            if (l > 1) Query(1,1,n,l - 1,r);
            else query(rt[0],1,n,r);
            printf("%d\n",(1 - ans + P) % P);
        }
    }
    return 0;
}

BZOJ4785 [Zjoi2017]树状数组 【二维线段树 + 标记永久化】

标签:cst   链接   stdout   大小   out   题解   改变   flag   map   

原文地址:https://www.cnblogs.com/Mychael/p/9047984.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!