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分做法。

 #include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define rep(i,l,r) for (int i=l; i<=r; i++)
typedef long long ll;
using namespace std; const int N=,md=;
int dp[N],n,m,op,l,r,cnt;
struct node{ int l,r,v; }G[N]; int ksm(int x,int y){
int res=;
for(; y; y>>=,x=(ll)x*x%md)
if (y&) res=(ll)res*x%md;
return res;
} int main(){
freopen("bit.in","r",stdin);
freopen("bit.out","w",stdout);
scanf("%d%d",&n,&m);
while (m--){
scanf("%d%d%d",&op,&l,&r);
if (op==) G[cnt++]=(node){l,r,ksm(r-l+,md-)};
else{
int ans=;
if(l==){
rep(i,,cnt)
if(G[i].r>=l){
int len=(G[i].r-max(l,G[i].l)+-(G[i].r>=r&&G[i].l<=r));
len = len*(ll)G[i].v%md;
ans=(((ll)ans*(-len)+(ll)(-ans)*len)%md+md)%md;
}
}else
rep(i,,cnt)
if(G[i].l<=l-&&G[i].r>=r){
int len=G[i].v*(ll)%md;
ans=(((ll)ans*(-len)+(ll)(-ans)*len)%md+md)%md;
}else if((G[i].l<=l-&&G[i].r>=l-)||(G[i].l<=r&&G[i].r>=r)){
int len=G[i].v;
ans=(((ll)ans*(-len)+(ll)(-ans)*len)%md+md)%md;
}
printf("%d\n",ans);
}
}
return ;
}

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

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

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

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

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

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

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

 #include<cstdio>
#include<algorithm>
#define lc (x<<1)
#define rc ((x<<1)|1)
#define rep(i,l,r) for (int i=l; i<=r; i++)
using namespace std; const int N=,md=;
int n,m,cnt,nd,op,l,r,rt[N*],P[N*];
struct D{ int ls,rs; }a[N*];
int F(int x,int y){ return (1ll*x*y%md+1ll*(-x+md)*(-y+md)%md)%md; } int ksm(int a,int b){
int res;
for (res=; b; a=(1ll*a*a)%md,b>>=)
if (b & ) res=(1ll*res*a)%md;
return res;
} void mdf(int &x,int L,int R,int l,int r,int k){
if (!x) x=++nd,P[x]=;
if (L==l && r==R) { P[x]=F(P[x],k); return; }
int mid=(L+R)>>;
if (r<=mid) mdf(a[x].ls,L,mid,l,r,k);
else if (l>mid) mdf(a[x].rs,mid+,R,l,r,k);
else mdf(a[x].ls,L,mid,l,mid,k),mdf(a[x].rs,mid+,R,mid+,r,k);
} int que(int x,int L,int R,int pos){
if (!x) return ;
if (L==R) return P[x]; int mid=(L+R)>>;
if (pos<=mid) return F(P[x],que(a[x].ls,L,mid,pos));
else return F(P[x],que(a[x].rs,mid+,R,pos));
} void Mdf(int x,int L,int R,int l,int r,int xx,int yy,int k){
if (L==l && r==R){ mdf(rt[x],,n+,xx,yy,k); return; }
int mid=(L+R)>>;
if (r<=mid) Mdf(lc,L,mid,l,r,xx,yy,k);
else if (l>mid) Mdf(rc,mid+,R,l,r,xx,yy,k);
else Mdf(lc,L,mid,l,mid,xx,yy,k),Mdf(rc,mid+,R,mid+,r,xx,yy,k);
} int Que(int x,int L,int R,int posx,int posy){
int res=;
if (rt[x]) res=F(res,que(rt[x],,n+,posy));
if (L==R) return res; int mid=(L+R)>>;
if (posx<=mid) res=F(res,Que(lc,L,mid,posx,posy));
else res=F(res,Que(rc,mid+,R,posx,posy));
return res;
} int main(){
freopen("bit.in","r",stdin);
freopen("bit.out","w",stdout);
scanf("%d%d",&n,&m);
rep(i,,m){
scanf("%d%d%d",&op,&l,&r);
if (op==){
int p=ksm(r-l+,md-);
if (l>) Mdf(,,n,,l-,l,r,(-p+md)%md),mdf(rt[],,n,,l-,);
if (r<n) Mdf(,,n,l,r,r+,n,(-p+md)%md),mdf(rt[],,n,r+,n,);
if (l!=r) Mdf(,,n,l,r,l,r,(-p*+md+md)%md);
mdf(rt[],,n,l,r,p);
}else{
if (l==) printf("%d\n",que(rt[],,n,r)); else printf("%d\n",Que(,,n,l-,r));
}
}
return ;
}

最新文章

  1. salesforce 零基础学习(四十九)自定义列表分页之使用Pagination实现分页效果 ※※※
  2. C# 对象转换为byte[] ,byte[]还原对象
  3. 走进AngularJs(七) 过滤器(filter) - 吕大豹
  4. dump buffer cache
  5. spark分片个数的确定及Spark内存错误(GC error)的迂回解决方式
  6. agile学习
  7. 解决xp下无法通过windows installer服务安装此安装程序包。您必须安装带有更新版本Wi
  8. ​C语言数组作为函数参数
  9. DB2数据库常用基本操作命令
  10. java中的String类常量池详解
  11. 给angularJs grid列上添加自定义按钮
  12. Array方面Js底层代码学习记录
  13. 一个简单的基于 DirectShow 的播放器 1(封装类)
  14. 如何利用HTTP缓存来加快你的网站应用
  15. PHP7.0-PHP7.3新特性与变更
  16. Git 头像修改 原
  17. maven+eclipse+jboss+oracle 12c+memcached+AngularJS
  18. Delta DVP 系列 PLC 各装置 Modbus 地址
  19. eclipse 大小写转换
  20. string format 格式化小数位

热门文章

  1. NYOJ 117 求逆序数 (树状数组)
  2. 常用的css3新特性总结
  3. zedboard学习记录.1.纯PL流水灯
  4. Spring Boot学习——单元测试
  5. 监控MYSQL主从同步配置中监控从库运行状态的脚本
  6. python多线程下载文件
  7. Pandas Installation
  8. mysql cursor游标的使用,实例
  9. 20165301 预备作业三:Linux安装及命令入门
  10. web 端 gantt组件选型