首页 > 其他分享 >P3688 [ZJOI2017] 树状数组 题解

P3688 [ZJOI2017] 树状数组 题解

时间:2024-09-05 22:15:19浏览次数:4  
标签:cnt NN int 题解 P3688 ZJOI2017 op qwq MOD

P3688 [ZJOI2017] 树状数组 题解

记录一下做这道题的心路历程,说明在没有事先知道“九条是求成了后缀和”的情况下如何发现,以及解释一些部分分的做法。

sub1,18pts:暴力搜索

无脑枚举,复杂度 \(\mathcal O(n^m)\)。

代码:

#include<bits/stdc++.h>
#define int long long
#define loop(i,a,b) for(int i=a;i<=b;i++)
#define pool(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int NN=1e5+5,MOD=998244353;
int n,m,ac[NN],wa[NN],p[NN];
int QP(int a,int b){
    int c=1;
    for(;b;b>>=1){
        if(b&1)c=1ll*c*a%MOD;
        a=1ll*a*a%MOD;
    }
    return c;
}
void AC_Add(int x){
    for(;x<=n;x+=x&-x)ac[x]^=1;
}
int AC_Ask(int x){
    if(x==0)return 0;
    int sum=0;
    for(;x;x-=x&-x)sum^=ac[x];
    return sum;
}
int AC_Query(int l,int r){
    return AC_Ask(r)^AC_Ask(l-1);
}
void WA_Add(int x){
    for(;x;x-=x&-x)wa[x]^=1;
}
int WA_Ask(int x){
    if(x==0)return 0;
    int sum=0;
    for(;x<=n;x+=x&-x)sum^=wa[x];
    return sum;
}
int WA_Query(int l,int r){
    return WA_Ask(r)^WA_Ask(l-1);
}
struct Oper{
    int op,l,r;
    void read(){
        cin>>op>>l>>r;
    }
}qwq[NN];
int ans[NN];
void DFS(int x,int cnt){
    if(x==m+1)return;
    if(qwq[x].op==2){
        ans[x]=(ans[x]+(AC_Query(qwq[x].l,qwq[x].r)==WA_Query(qwq[x].l,qwq[x].r))*p[cnt])%MOD;
        DFS(x+1,cnt);
    }else{
        loop(i,qwq[x].l,qwq[x].r){
            AC_Add(i),WA_Add(i);
            DFS(x+1,cnt+1);
            AC_Add(i),WA_Add(i);
        }
    }
    return;
}
signed main(){
    cin>>n>>m;
    int cnt=0;
    p[0]=1;
    loop(i,1,m){
        qwq[i].read();
        if(qwq[i].op==1){
            cnt++;
            p[cnt]=1ll*p[cnt-1]*QP(qwq[i].r-qwq[i].l+1,MOD-2)%MOD;
        }
    }
    DFS(1,0);
    loop(i,1,m)if(qwq[i].op==2)cout<<ans[i]%MOD<<"\n";
    return 0;
}

但这份代码并不是一无是处,它可以帮我们对拍,并且我们的思路也将从这开始

sub1~3,48pts:DP

根据上述搜索,我们应该考虑记忆化进行剪枝,发现统计答案的时候只与AC_Query(qwq[x].l,qwq[x].r)==WA_Query(qwq[x].l,qwq[x].r)这个值有关,记为 \(d\),所以我们可以考虑将其记录下来,并且注意到各个询问之间是没有关系的,我们可以枚举并分别处理。

对于第 \(id\) 个询问,记录 \(f_{i}\) 表示进行前 \(i\) 次操作后 \(d=0\) 的概率, \(p\) 表示第 \(i\) 个操作对 \(d\) 造成变化的概率,则有 \(f_{i}=p(1-f_{i-1})+(1-p)f_{i-1}\),那么如何求得 \(p\)?这就要看九条的错误代码的性质:

我们发现其查询(红色)是从左下到右上,修改(蓝色)是从右下到左上,而这两者相交的充要条件就是蓝色线条的起点在红色线条起点的右边,即如果一个修改操作在一个查询操作的右边,就会产生贡献,那不就是后缀和吗?

于是 AC_Query 就是查询 \([l,r]\) 的值, WA_Query 就是查询 \([l-1,r-1]\) 的值,那么使得答案不同当且仅当修改了 \(l-1,r\) 这两个位置,枚举每个询问,假设包含了 \(x\) 个,则 \(p=\frac{x}{R-L+1}\)。

注意当 \(l=1\) 的时候需要特判,此时  AC_Query 查询 \([1,r]\) 的值,WA_Query 查询 \([r,n]\) 的值,那么使得答案不同当且仅当没有修改 \(r\),\(p=\frac{R-L+1-[L\le r\le R]}{R-L+1}\)。

复杂度 \(\mathcal O(nm)\)。

代码如下:

#include<bits/stdc++.h>
#define loop(i,a,b) for(int i=a;i<=b;i++)
#define pool(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int NN=1e5+5,MOD=998244353;
int n,m,f;
int QP(int a,int b){
    int c=1;
    for(;b;b>>=1){
        if(b&1)c=1ll*c*a%MOD;
        a=1ll*a*a%MOD;
    }
    return c;
}
struct Oper{
    int op,l,r;
    void read(){cin>>op>>l>>r;}
}qwq[NN];
struct Seq{int t,l,r;};
vector<Seq> vec_qry;
int ans[NN];
int In(Oper b,int x){return b.l<=x&&x<=b.r;}
int main(){
    cin>>n>>m;
    loop(i,1,m){
        qwq[i].read();
        if(qwq[i].op==2)vec_qry.push_back({i,qwq[i].l,qwq[i].r});
    }
    for(Seq qry:vec_qry){
        f=1;
        loop(i,1,qry.t-1){
            int cnt=0;
            if(qwq[i].op==2)continue;
            if(qry.l!=1)cnt=In(qwq[i],qry.r)+In(qwq[i],qry.l-1);
            else cnt=qwq[i].r-qwq[i].l+1-(qwq[i].l<=qry.r&&qry.r<=qwq[i].r);
            int p=1ll*cnt*QP(qwq[i].r-qwq[i].l+1,MOD-2)%MOD;
            f=(f+p-2ll*p*f%MOD+MOD)%MOD;
        }
        cout<<(f+MOD)%MOD<<"\n";
    }
    return 0;
}

All,100pts:二维线段树

考虑到 \(0\le x\le 2\),我们可以直接分类讨论!

  • \(l-1\in [1,L-1],r\in[1,L-1]\cup[R+1,n]\implies p=0\)

  • \(l-1\in [1,L-1],r\in[L,R]\implies p=\frac 1{R-L+1}\)

  • \(l-1\in [L,R],r\in [L,R]\implies p=\frac2{R-L+1}\)

  • \(l-1\in [L,R],r\in [R+1,n]\implies p=\frac 1{R-L+1}\)

  • \(l-1\in [R+1,n],r\in [R+1,n]\implies p=\frac 1{R-L+1}\)

  • \(l-1=0,r\in [1,L-1]\cup[R+1,n]\implies p=1\)

  • \(l-1=0,r\in [L,R]\implies p=\frac{R-L}{R-L+1}\)

这就变为了一个二维数点问题。

因为转移 $ \begin{bmatrix}1-p,p\p,1-p\end{bmatrix}\begin{bmatrix}f_{i-1}\1-f_{i-1}\end{bmatrix}=\begin{bmatrix}f_{i}\1-f_{i}\end{bmatrix}$ 形如一个对称矩阵,有结合律,于是可以直接用线段树进行维护。

复杂度 \(\mathcal O(m\log^2n)\)。

代码如下:

#include<bits/stdc++.h>
#define loop(i,a,b) for(int i=a;i<=b;i++)
#define pool(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int NN=1e5+5,MOD=998244353;
int n,m;
int QP(int a,int b){
	int c=1;
	for(;b;b>>=1){
		if(b&1)c=1ll*c*a%MOD;
		a=1ll*a*a%MOD;
	}
	return c;
}
struct SegTr{
	int ls,rs,v;
	#define ls(x) sgt[x].ls
	#define rs(x) sgt[x].rs
	#define v(x) sgt[x].v
}sgt[NN*400];
int rt[NN<<2],tot;
int Mul(int p,int q){return (1-p-q+2ll*p*q%MOD)%MOD;}
void PruneY(int&p,int l,int r,int v,int L=1,int R=n){
	if(l>R||L>r||l>r)return;
	if(!p)p=++tot,v(p)=1;
	if(l<=L&&R<=r){v(p)=Mul(v(p),v);return;}
	int mid=L+R>>1;
	PruneY(ls(p),l,r,v,L,mid);
	PruneY(rs(p),l,r,v,mid+1,R);
	return;
}
void PruneX(int p,int l,int r,int inl,int inr,int v,int L=0,int R=n){
	if(l>R||L>r||l>r)return;
	if(l<=L&&R<=r)return PruneY(rt[p],inl,inr,v);
	int mid=L+R>>1;
	PruneX(p<<1,l,r,inl,inr,v,L,mid);
	PruneX(p<<1|1,l,r,inl,inr,v,mid+1,R);
	return;
}
int PickY(int p,int pos,int L=1,int R=n){
	if(!p)return 1;
	if(L==R)return v(p);
	int mid=L+R>>1;
	if(pos<=mid)return Mul(PickY(ls(p),pos,L,mid),v(p));
	else		return Mul(PickY(rs(p),pos,mid+1,R),v(p));
}
int PickX(int p,int pos,int inpos,int L=0,int R=n){
	if(L==R)return PickY(rt[p],inpos);
	int mid=L+R>>1;
	int tmp=PickY(rt[p],inpos);
	if(pos<=mid)return Mul(PickX(p<<1,pos,inpos,L,mid),tmp);
	else		return Mul(PickX(p<<1|1,pos,inpos,mid+1,R),tmp);
}
int Mod(int p){return (p%MOD+MOD)%MOD;}
signed main(){
	cin>>n>>m;
	loop(i,1,m){
		int op,l,r;cin>>op>>l>>r;
		if(op==1){
			int p=QP(r-l+1,MOD-2);
			PruneX(1,1,l-1,l,r,Mod(1-p)),PruneX(1,0,0,1,l-1,0);
			PruneX(1,l,r,r+1,n,Mod(1-p)),PruneX(1,0,0,r+1,n,0);
			PruneX(1,l,r,l,r,Mod(1-2*p)),PruneX(1,0,0,l,r,p);
		}
		else cout<<Mod(PickX(1,l-1,r))<<"\n";
	}
	return 0;
}

标签:cnt,NN,int,题解,P3688,ZJOI2017,op,qwq,MOD
From: https://www.cnblogs.com/lupengheyyds/p/18399310

相关文章

  • AT_arc151 题解 & 数组字典序大小比较求方案数
    很好的一题,做的时候没有一点思路,看了题解。看来做过的题目还是太少了,记录一下经验。注意到$1\leN\le2\times10^5$和$1\leM\le10^9$,如此庞大的数据,dp是肯定不行的。当字典序$A<B$时,当且仅当存在$i$,使得$\forallx\in[1,i)$,$A_x=B_x$且$A_i<B_i$。那么我们对于$......
  • CF704B Ant Man 题解
    题目传送门前置知识预设性DP解法考虑统计每个数单独的贡献,然后进行预设性DP。设\(f_{i,j}\)表示当前填了\([1,i]\)时有\(j\)个连续段的最小权值,边界为\(f_{0,0}=0\)。对\(i(i\nes,i\nee)\)填入的位置进行分讨。新开一段后面填入的数都比\(i\)大(如果存......
  • 洛谷 P1516 青蛙的约会 题解
    一道简单的数学题~首先分析题意。精简得出:假设跳了\(t\)次,那么青蛙A的坐标是\((x+mt)\modL\),青蛙B的坐标是\((y+nt)\modL\),列出方程:\[x+mt\equivy+nt\pmodL\]由于余数具有可减性,所以把\(y+nt\)移到左边,得出:\[x-y+mt-nt\equiv0\pmodL\]写成人话:\[(x-y+mt-nt)\mod......
  • 9.5 上午 becoder 模拟赛总结 & 题解
    T1文本编辑器说实话,看到题目的第一瞬间,我还以为gm第一道就放了平衡树。一道链表的模板题,当然愿意也可以用平衡树写,不多说了,直接放代码(100pts):#defineN1000005chars[N],t[N];intnow,pre[N],nxt[N];intmain(){scanf("%s%s",s+1,t+1);intn=strlen(s+1);......
  • 【转载】P1399 [NOI2013] 快餐店 题解
    作者%%%%%%NightTide%%%%%%题目大意求一棵基环树的重心。即一个点,使得树上到其距离最长的点到其的距离最短。注意,这个点不一定是一个节点,可以在树上的任意位置。输出树上到其距离最长的点到其的距离。或者说求基环树最短的直径?(大雾解题思路显然,这颗基环树的直径只有两......
  • [USACO13OPEN] Photo G 题解
    前言题目链接:洛谷。题意简述一个长度为\(n\)的序列,有一些位置染了色。现给出\(m\)条限制,第\(i\)条限制为\(l_i\simr_i\)中有且仅有一个位置染色。求出满足这\(m\)中条件,染色位置个数最多为多少。\(n\leq2\times10^5\),\(m\leq10^5\)。题目分析方法\(1\):差......
  • Baby Ehab Plays with Permutations 题解
    前言题目链接:Codeforces;洛谷。题意简述你有一个长度为\(n\)的序列\(p\)满足\(p_i=i\),你可以进行\(x\)次操作,每次操作找到两个不同的\(i,j\)并且交换\(p_i,p_j\),问最终有几个可能的序列。分别求出\(x=1,\ldots,k\)时的答案。\(1\len\le10^9\),\(1\lek\le......
  • Marbles 题解
    前言题目链接:Codeforces;洛谷。题意简述给定长度为\(n\)的序列\(a\),你可以交换相邻元素,请问最少交换多少次使得序列连续,即对于每种颜色,其在序列中出现的位置都是连续一段。\(m=\max\{a_i\}\leq20\),\(n\leq4\times10^5\)。题目分析对于“连续的序列”,不放看做......
  • [POI2014] RAJ-Rally 题解
    前言题目链接:Hydro&bzoj;黑暗爆炸;洛谷。题意简述DAG求删点后最长路的最小值。\(n\leq5\times10^5\),\(m\leq10^6\)。题目分析其实对于删点/边加查询最长/短路的套路是有的。比如:故乡的梦、桥。本题也类似。我们考虑,如果删除的边不在原来最长路上,那么删之后的......
  • CF1941场题解
    RudolfandtheTicket算法:枚举。题意简述:从\(a\)数组中和\(b\)数组中各选出一个数,使得它们的和不超过\(k\),求选法数量。考虑直接枚举每一种可能的搭配即可。Rudolfand121算法:贪心。题意简述:定义一次操作为,该位置上的数减去\(2\),其前一个和后一个位置(必须存在)上的数......