目录
显示
题目链接
https://www.luogu.com.cn/problem/P5280
题解
我们按照题目中的伪代码,将能遍历到的点分为三类(遍历不到的点 \(tag\) 不变,所以不作讨论):
- 完全覆盖的点:这个点对应的区间是指定询问区间的子集。
- 完全没覆盖的点:这个点对应的区间和指定询问区间的交集为空。
- 半覆盖的点:不属于上述两类的点。
根据代码,递归遍历到半覆盖的点时会继续向下遍历,而遇到其他两类点时会终止遍历。
写成代码的形式是这样的:
void update(int rt,int cl,int cr,int l,int r) { if(r<cl||cr<l)return;//完全没覆盖的点 if(l<=cl&&cr<=r)//完全覆盖的点 { tag[rt]=1; return; } //半覆盖的点 pushdown(rt);//显然这里被更新的点下面都会遍历到 int mid=(cl+cr)>>1; update(rt<<1,cl,mid,l,r); update(rt<<1|1,mid+1,cr,l,r); }
因为线段树只会复制,形态不会改变,因此我们可以在一棵线段树里维护每个节点的信息。
设 \(f_i\) 表示 \(i\) 号节点 \(tag\) 的期望值。
(所有节点的期望值加起来,再乘以树的总数,就可以得到所求答案了)
我们分类讨论下:
- 对于完全覆盖的点:经过修改后这个点的 \(tag\) 一定为 \(1\),从而有 \(f_i=(f_i+1)/2\);
- 对于半覆盖的点:因为有 pushdown,这个点的 \(tag\) 一定为零,从而有 \(f_i=f_i/2\);
- 对于完全没覆盖的点:仅凭现有的信息无法计算。一个完全没覆盖的点是否打上 \(tag\),取决于这个点到根节点的路径上是否存在一个有 \(tag\) 的点(这样就可以通过 pushdown 的方式使该点获取到 \(tag\)),我们需要多维护一些信息来进行计算。
于是我们设 \(g_i\) 表示 \(i\) 号节点到根节点的路径上有 \(tag\) 的概率。
继续分类讨论:
- 对于完全覆盖的点:经过修改后该点一定有 \(tag\),从而该点所在子树的所有点都有 \(g_i=(g_i+1)/2\);
- 对于半覆盖的点:其到根节点的所有点标记都已经下放,从而没有标记,即 \(g_i=g_i/2\);
- 对于完全没覆盖的点:如果其到根节点的路径上有 \(tag\),则这个点会带上 \(tag\),否则这个点以及这个点到根节点的路径上都没有标记,即 \(f_i=(f_i+g_i)/2\)(根据上面的描述,\(g_i\) 的值不会改变)。
我们发现一次修改操作最多更新 \(O(\log n)\) 个 \(f\) 的值,但是 \(g\) 的值最坏情况下(因为有对一个子树内所有点的更新操作)会更新 \(O(n)\) 个。
为了防止这种事情发生,我们需要打标记来处理“修改一个子树的 \(g\) 值”这一操作。
向 \(i\) 号点下放一个修改 \(x\) 次的标记的时候,将 \(g_i\) 作如下处理:
$$g_i=(g_i+2^x-1)/2^x$$
这样这题就做完了。
下面这份代码目前在 loj 上跑的是最快的。
#include <iostream> #include <algorithm> #define MOD 998244353 #define INV2 499122177 using namespace std; struct seg { long long f,g,s,tag; }s[800005]; long long p[100005],invp[100005]; void pushup(int rt) { s[rt].s=(s[rt<<1].s+s[rt<<1|1].s+s[rt].f)%MOD; } void pushdown(int rt) { int tag=s[rt].tag; s[rt<<1].tag+=tag; s[rt<<1|1].tag+=tag; s[rt<<1].g=(s[rt<<1].g+p[tag]-1)*invp[tag]%MOD; s[rt<<1|1].g=(s[rt<<1|1].g+p[tag]-1)*invp[tag]%MOD; s[rt].tag=0; } void update(int rt,int cl,int cr,int l,int r) { if(cr<l||r<cl) { s[rt].f=(s[rt].f+s[rt].g)*INV2%MOD; pushup(rt); return; } if(l<=cl&&cr<=r) { s[rt].f=(s[rt].f+1)*INV2%MOD; s[rt].g=(s[rt].g+1)*INV2%MOD; s[rt].tag++; pushup(rt); return; } s[rt].f=s[rt].f*INV2%MOD; s[rt].g=s[rt].g*INV2%MOD; pushdown(rt); int mid=(cl+cr)>>1; update(rt<<1,cl,mid,l,r); update(rt<<1|1,mid+1,cr,l,r); pushup(rt); } int main() { ios::sync_with_stdio(false); int n,m; cin>>n>>m; p[0]=invp[0]=1; for(int i=1;i<=m;i++) { p[i]=p[i-1]*2%MOD; invp[i]=invp[i-1]*INV2%MOD; } int cnt=0; while(m--) { int op; cin>>op; if(op==1) { int l,r; cin>>l>>r; update(1,1,n,l,r); cnt++; } else cout<<s[1].s*p[cnt]%MOD<<endl; } return 0; }