zoukankan      html  css  js  c++  java
  • [BZOJ4785][ZJOI2017]树状数组(概率+二维线段树)

     

    4785: [Zjoi2017]树状数组

    Time Limit: 40 Sec  Memory Limit: 512 MB
    Submit: 297  Solved: 195
    [Submit][Status][Discuss]

    Description

     漆黑的晚上,九条可怜躺在床上辗转反侧。难以入眠的她想起了若干年前她的一次悲惨的OI 比赛经历。那是一道

    基础的树状数组题。给出一个长度为 n 的数组 A,初始值都为 0,接下来进行 m 次操作,操作有两种:
    1 x,表示将 Ax 变成 (Ax + 1) mod 2。
    2 l r,表示询问 sigma(Ai) mod 2,L<=i<=r
    尽管那个时候的可怜非常的 simple,但是她还是发现这题可以用树状数组做。当时非常young 的她写了如下的算
    法:
    1: function Add(x)
    2: while x > 0 do
    3: A
    x ← (Ax + 1) mod 2
    4: x ← x ? lowbit(x)
    5: end while
    6: end function
    7:
    8: function Find(x)
    9: if x == 0 then
    10: return 0
    11: end if
    12: ans ← 0
    13: while x ≤ n do
    14: ans ← (ans + Ax) mod 2
    15: x ← x + lowbit(x)
    16: end while
    17: return ans
    18: end function
    19:
    20: function Query(l, r)
    21: ansl ← Find(l ? 1)
    22: ansr ← Find(r)
    23: return (ansr ? ansl + 2) mod 2
    24: end function
    其中 lowbit(x) 表示数字 x 最?的非 0 二进制位,例如 lowbit(5) = 1, lowbit(12) = 4。进行第一类操作的时
    候就调用 Add(x),第二类操作的时候答案就是 Query(l, r)。如果你对树状数组比较熟悉,不难发现可怜把树状
    数组写错了: Add和Find 中 x 变化的方向反了。因此这个程序在最终测试时华丽的爆 0 了。然而奇怪的是,在
    当时,这个程序通过了出题人给出的大样例——这也是可怜没有进行对拍的原因。现在,可怜想要算一下,这个程
    序回答对每一个询问的概率是多少,这样她就可以再次的感受到自己是一个多么非的人了。然而时间已经过去了很
    多年,即使是可怜也没有办法完全回忆起当时的大样例。幸运的是,她回忆起了大部分内容,唯一遗忘的是每一次
    第一类操作的 x的值,因此她假定这次操作的 x 是在 [li, ri] 范围内 等概率随机 的。具体来说,可怜给出了
    一个长度为 n 的数组 A,初始为 0,接下来进行了 m 次操作:
    1 l r,表示在区间 [l, r] 中等概率选取一个 x 并执行 Add(x)。
    2 l r,表示询问执行 Query(l, r) 得到的结果是正确的概率是多少。

    Input

    第一行输入两个整数 n, m。
    接下来 m 行每行描述一个操作,格式如题目中所示。
    N<=10^5,m<=10^5,1<=L<=R<=N

    Output

    对于每组询问,输出一个整数表示答案。如果答案化为最简分数后形如 x/y
    ,那么你只需要输出 x*y^?1 mod 998244353 后的值。(即输出答案模 998244353)。

    Sample Input

    5 5
    1 3 3
    2 3 5
    2 4 5
    1 1 3
    2 2 5

    Sample Output

    1
    0
    665496236
    //在进行完 Add(3) 之后, A 数组变成了 [0, 1, 1, 0, 0]。所以前两次询问可怜的程序答案都是
    1,因此第一次询问可怜一定正确,第二次询问可怜一定错误。

     首先经过分析证明可得,树状数组只是一层外衣,实际上题目就是求[l-1,r-1]和[l,r]的改变次数的差为偶数的概率,也就是l-1和r改变次数差为偶数的概率。(l==1的情况要特殊处理,也就是[1,r-1]和[r+1,n]的总改变次数差为偶数的概率)

    想到这里之后,我们会有一个看似正确的直觉:可以通过动规+前缀和求出每个数被改变奇数次和偶数次的概率。但是实际上由于动规方程里的并不是互斥事件,所以概率不可以直接相乘。

    所以我们可以肯定,一定是对每个数,依次遍历所有的修改,已修改时间为下标做DP。这样就有了一个简单的50分做法。

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<iostream>
     4 #include<algorithm>
     5 #define rep(i,l,r) for (int i=l; i<=r; i++)
     6 typedef long long ll;
     7 using namespace std;
     8 
     9 const int N=3005,md=998244353;
    10 int dp[N],n,m,op,l,r,cnt;
    11 struct node{ int l,r,v; }G[N];
    12 
    13 int ksm(int x,int y){
    14     int res=1;
    15     for(; y; y>>=1,x=(ll)x*x%md)
    16         if (y&1) res=(ll)res*x%md;
    17     return res;
    18 }
    19 
    20 int main(){
    21     freopen("bit.in","r",stdin);
    22     freopen("bit.out","w",stdout);
    23     scanf("%d%d",&n,&m);
    24     while (m--){
    25         scanf("%d%d%d",&op,&l,&r);
    26         if (op==1) G[cnt++]=(node){l,r,ksm(r-l+1,md-2)};
    27         else{
    28             int ans=1;
    29             if(l==1){
    30                 rep(i,0,cnt)
    31                     if(G[i].r>=l){
    32                         int len=(G[i].r-max(l,G[i].l)+1-(G[i].r>=r&&G[i].l<=r));
    33                         len = len*(ll)G[i].v%md;
    34                         ans=(((ll)ans*(1-len)+(ll)(1-ans)*len)%md+md)%md;
    35                     }
    36             }else
    37                 rep(i,0,cnt)
    38                     if(G[i].l<=l-1&&G[i].r>=r){
    39                         int len=G[i].v*(ll)2%md;
    40                         ans=(((ll)ans*(1-len)+(ll)(1-ans)*len)%md+md)%md;
    41                     }else if((G[i].l<=l-1&&G[i].r>=l-1)||(G[i].l<=r&&G[i].r>=r)){
    42                         int len=G[i].v;
    43                         ans=(((ll)ans*(1-len)+(ll)(1-ans)*len)%md+md)%md;
    44                     }
    45             printf("%d
    ",ans);
    46         }
    47     }
    48     return 0;
    49 }

    那么如果想到了这里,已经不难进一步想出用数据结构来维护了。将每个询问映射成二维平面上的点(l-1,r),(类似BZOJ4826影魔),然后用二维线段树实现矩形区间修改,l==1的情况只要一维线段树即可。

    UOJ上可能有BUG,在线自定义测试的时候程序正常运行并返回正确结果,同样的数据提交评测却无限RE。能过官方数据,BZOJ上AC,UOJ上的HACK数据没有测。

    现在总结一下写代码的时候需要注意的地方。

    1.每写完一段停下来检查一下

    2.修改程序的时候要慢一点,检查修改是否正确并思考是否有类似地方需要修改。

    3.调试时多想想平时遇到的问题优先考虑。

    4.多总结遇到的错误(特别是模板方面),以1A为最终目标。

     1 #include<cstdio>
     2 #include<algorithm>
     3 #define lc (x<<1)
     4 #define rc ((x<<1)|1)
     5 #define rep(i,l,r) for (int i=l; i<=r; i++)
     6 using namespace std;
     7 
     8 const int N=200100,md=998244353;
     9 int n,m,cnt,nd,op,l,r,rt[N*3],P[N*180];
    10 struct D{ int ls,rs; }a[N*180];
    11 int F(int x,int y){ return (1ll*x*y%md+1ll*(1-x+md)*(1-y+md)%md)%md; }
    12 
    13 int ksm(int a,int b){
    14     int res;
    15     for (res=1; b; a=(1ll*a*a)%md,b>>=1)
    16         if (b & 1) res=(1ll*res*a)%md;
    17     return res;
    18 }
    19 
    20 void mdf(int &x,int L,int R,int l,int r,int k){
    21     if (!x) x=++nd,P[x]=1;
    22     if (L==l && r==R) { P[x]=F(P[x],k); return; }
    23     int mid=(L+R)>>1;
    24     if (r<=mid) mdf(a[x].ls,L,mid,l,r,k);
    25     else if (l>mid) mdf(a[x].rs,mid+1,R,l,r,k);
    26         else mdf(a[x].ls,L,mid,l,mid,k),mdf(a[x].rs,mid+1,R,mid+1,r,k);
    27 }
    28 
    29 int que(int x,int L,int R,int pos){
    30     if (!x) return 1;
    31     if (L==R) return P[x]; int mid=(L+R)>>1;
    32     if (pos<=mid) return F(P[x],que(a[x].ls,L,mid,pos));
    33         else return F(P[x],que(a[x].rs,mid+1,R,pos));
    34 }
    35 
    36 void Mdf(int x,int L,int R,int l,int r,int xx,int yy,int k){
    37     if (L==l && r==R){ mdf(rt[x],0,n+1,xx,yy,k); return; }
    38     int mid=(L+R)>>1;
    39     if (r<=mid) Mdf(lc,L,mid,l,r,xx,yy,k);
    40     else if (l>mid) Mdf(rc,mid+1,R,l,r,xx,yy,k);
    41         else Mdf(lc,L,mid,l,mid,xx,yy,k),Mdf(rc,mid+1,R,mid+1,r,xx,yy,k);
    42 }
    43 
    44 int Que(int x,int L,int R,int posx,int posy){
    45     int res=1;
    46     if (rt[x]) res=F(res,que(rt[x],0,n+1,posy));
    47     if (L==R) return res; int mid=(L+R)>>1;
    48     if (posx<=mid) res=F(res,Que(lc,L,mid,posx,posy));
    49         else res=F(res,Que(rc,mid+1,R,posx,posy));
    50     return res;
    51 }
    52 
    53 int main(){
    54     freopen("bit.in","r",stdin);
    55     freopen("bit.out","w",stdout);
    56     scanf("%d%d",&n,&m);
    57     rep(i,1,m){
    58         scanf("%d%d%d",&op,&l,&r);
    59         if (op==1){
    60             int p=ksm(r-l+1,md-2);
    61             if (l>1) Mdf(1,1,n,1,l-1,l,r,(1-p+md)%md),mdf(rt[0],1,n,1,l-1,0);
    62             if (r<n) Mdf(1,1,n,l,r,r+1,n,(1-p+md)%md),mdf(rt[0],1,n,r+1,n,0);
    63             if (l!=r) Mdf(1,1,n,l,r,l,r,(1-p*2+md+md)%md);
    64             mdf(rt[0],1,n,l,r,p);
    65         }else{
    66             if (l==1) printf("%d
    ",que(rt[0],1,n,r)); else printf("%d
    ",Que(1,1,n,l-1,r));
    67         }
    68     }
    69     return 0;
    70 }
  • 相关阅读:
    实例属性 类属性 实例域 类域
    研究数据集
    static 静态域 类域 静态方法 工厂方法 he use of the static keyword to create fields and methods that belong to the class, rather than to an instance of the class 非访问修饰符
    accessor mothod mutator mothod 更改器方法 访问器方法 类的方法可以访问类的任何一个对象的私有域!
    上钻 下钻 切片 转轴 降采样
    识别会话
    Performance Tuning Using Linux Process Management Commands
    Secure Hash Algorithm 3
    grouped differently across partitions
    spark 划分stage Wide vs Narrow Dependencies 窄依赖 宽依赖 解析 作业 job stage 阶段 RDD有向无环图拆分 任务 Task 网络传输和计算开销 任务集 taskset
  • 原文地址:https://www.cnblogs.com/HocRiser/p/8543389.html
Copyright © 2011-2022 走看看