• 【洛谷3688】[ZJOI2017] 树状数组(二维线段树)


    点此看题面

    • 给定一个长度为(n)的数组,有两种操作:
      • 随机给区间([l,r])中某一位置的值加上(1)
      • 询问区间([l,r])所有位置的和模(2)的余数。
    • 对于每个询问,求如果把树状数组写反了,与正确的答案相同的概率。
    • (nle10^5)

    感觉这道题应该不是很难吧,至少我都能自己做出来。

    不过这可能也是得益于不小心看到了二维线段树这一标签。。。

    题意转化

    注意这里的询问是以(2)为模数的,而(xequiv -x(mod 2))

    发现树状数组写反了,就意味着现在得到的是区间([l-1,r-1])所有位置的和。

    那么也就是求(a_{l-1})(a_r)相同的概率。

    二维线段树

    考虑我们用((i,j))表示(i)(j)两个位置值相同的概率。

    对于一次修改([l,r]),令(t=frac{1}{r-l+1}),然后发现对于((i,j))有以下几种变化的可能:

    • (i,j)都不在([l,r])范围内,无影响。
    • (i,j)都在([l,r])范围内,有(2t)的概率选中其中一个数导致值改变,(1-2t)的概率没选中。
    • (i,j)有且仅有一个在([l,r])范围内,有(t)的概率选择区间中那个数导致值改变,(1-t)的概率没选中。

    考虑如何合并两个值不变的概率(x,y),容易发现就是(x imes y+(1-x) imes(1-y))。(即要么都不变,要么都变)

    那么就可以直接拿二维线段树维护了。

    具体实现时为了好写,可以考虑标记永久化

    (l=1)的特殊情况

    注意,其实我们还要特殊处理(l=1),即(l-1=0)的情况。

    此时得到的应该是([r,n])所有位置的和,而本应得到的是([1,r])所有位置的和,也就是要求出除第(r)个位置以外所有数之和为(0)的概率。

    可以发现一个位置与(0)的答案值不变的概率就是它作为被修改位置的概率。

    这其实和一般情况还是基本一致的,只要单独对这种情况开一个(0)号节点就好了,省得重写一个线段树。

    代码:(O(nlog^2n))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100000
    #define LN 20
    #define X 998244353
    using namespace std;
    int n;I int G(CI x,CI y) {return (1LL*x*y+1LL*(1-x+X)*(1-y+X))%X;}//合并两个值不变的概率
    I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
    class SegSegTree//标记永久化二维线段树
    {
    	private:
    		#define PT CI l=0,CI r=n
    		#define LT l,mid
    		#define RT mid+1,r
    		int Rt[N<<2];class SegmentTree
    		{
    			private:
    				int Nt;struct node{int V,S[2];}O[N*LN*LN+5];
    			public:
    				I void U(int& rt,CI L,CI R,CI v,PT)//区间修改
    				{
    					if(!rt&&(O[rt=++Nt].V=1),L<=l&&r<=R) return (void)(O[rt].V=G(O[rt].V,v));
    					RI mid=l+r>>1;L<=mid&&(U(O[rt].S[0],L,R,v,LT),0),R>mid&&(U(O[rt].S[1],L,R,v,RT),0);
    				}
    				I int Q(CI rt,CI y,PT)//单点询问
    				{
    					if(!rt) return 1;if(l==r) return O[rt].V;RI mid=l+r>>1;
    					return G(y<=mid?Q(O[rt].S[0],y,LT):Q(O[rt].S[1],y,RT),O[rt].V);
    				}
    		}S;
    	public:
    		I void U(CI L,CI R,CI _l,CI _r,CI v,PT,CI rt=1)//区间修改
    		{
    			if(L<=l&&r<=R) return S.U(Rt[rt],_l,_r,v);RI mid=l+r>>1;
    			L<=mid&&(U(L,R,_l,_r,v,LT,rt<<1),0),R>mid&&(U(L,R,_l,_r,v,RT,rt<<1|1),0);
    		}
    		I int Q(CI x,CI y,PT,CI rt=1)//单点询问
    		{
    			if(l==r) return S.Q(Rt[rt],y);RI mid=l+r>>1;
    			return G(x<=mid?Q(x,y,LT,rt<<1):Q(x,y,RT,rt<<1|1),S.Q(Rt[rt],y));
    		}
    }T;
    int main()
    {
    	RI Qt,op,x,y,t;scanf("%d%d",&n,&Qt);W(Qt--) switch(scanf("%d%d%d",&op,&x,&y),op)
    	{
    		case 1:t=QP(y-x+1,X-2),T.U(x,y,x,y,(1-2LL*t%X+X)%X),T.U(0,0,x,y,t),//两个都在区间中
    			x^1&&(T.U(1,x-1,x,y,(1-t+X)%X),T.U(0,0,1,x-1,0),0),//有且仅有一个
    			y^n&&(T.U(x,y,y+1,n,(1-t+X)%X),T.U(0,0,y+1,n,0),0);break;//有且仅有一个
    		case 2:printf("%d
    ",T.Q(x-1,y));break;//查询x-1和y值相同的概率
    	}return 0;
    }
    
  • 相关阅读:
    [转载]C# 判断字符是否中文还是英文
    [转载]C#读写配置文件(XML文件)
    [转载]C#多线程学习(一) 多线程的相关概念
    [转载]C# HashTable 遍历与排序
    [转载]C# 多选功能(checkedListBox控件)
    [转载]在C#中使用官方驱动操作MongoDB
    [转载]MongoDB开发学习(2)索引的基本操作
    公钥私钥和RSA算法
    iOS 如何在一个已经存在多个project的workspace中引入cocoapods管理第三方类库
    应用号
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu3688.html
Copyright © 2020-2023  润新知