【洛谷5280】[ZJOI2019]线段树(线段树)
题目
分析
一道加深对线段树理解的好题。(其实并没有什么用处)
注意,每次操作是先把所有线段树复制一份,只有旧的线段树进行了 Modify ,而新的线段树保持原样。
先转化一下题意。如果直接维护某一个结点在多少棵线段树上是有 tag 的,那么每次修改时,没有被访问到的结点答案都要乘 \(2\) ,时间复杂度会爆掉。而如果改成每次有 \(\frac{1}{2}\) 的概率进行修改,维护某一个结点有 tag 的概率(相当于原题中该结点有 tag 的树的数量占总数之比),没有被访问到的结点就不需要修改了。
现在,设 \(u\) 有 tag 的概率是 \(f_{u,0}\) ,来仔细分析一下 Modify 时发生了什么。出于某种原因(以下会提到),还要维护 \(f_{u,1}\) 表示从根到 \(u\) 的路径上(不含 \(u\) )有 tag 且 \(u\) 没有 tag 的概率。方便起见,再设 \(f_{u,2}=1-f_{u,0}-f_{u,1}\) 。
一
对于 直接 修改到的结点(就是被修改区间完全覆盖而父亲不被修改区间完全覆盖的结点),如果原来有 tag (概率为\(f_{u,0}\) )还有 tag ,对于原来没有 tag 的情况(概率为 \(1-f_{u,0}\))有 \(\frac{1}{2}\) 的概率被修改而打上 tag ,即:
二
对于直接修改到的结点的父亲(就是与修改区间有交但是不被完全覆盖的结点),被 pushdown 强制去掉了 tag 。也就是当原来有 tag 且本次修改没有进行时才有 tag ,即:
三
对于父亲属于第二类点而本身不与修改区间有交的点,这些点接收了来自父亲的 pushdown ,因此如果修改前在从根到它的路径上(不含本身)有 tag 那么它就会被打上 tag ,否则如果它自己本来有 tag 就有 tag ,否则没有,即:
以上三种情况中的结点的祖先都全部被 pushdown 了,所以只有当修改没有发生时从根到这些结点的路径上才可能有 tag ,即:
四
对于直接修改到的结点子树中的点(不含自身)(就是被修改区间完全覆盖且父亲也被修改区间完全覆盖的结点),tag 的有无没有发生变化,而如果修改成功进行了,这些结点全部都满足到根的路径上有 tag (因为直接修改到的结点一定有 tag ),但要排除掉这个点本身有 tag 的情况 (请回去看 \(f_{u,1}\) 的定义),即:
五
其他结点的 tag 不会有变化,从它到根的路径上的 tag 的 存在性 也没有变化(位置可能被 pushdown 了),所以什么都不需要改。
综合以上分析,前三类的总数是 \(O(\log n)\) ,可以直接修改;第五类不需要改。而对于第四类,每次修改的是一棵子树,且只修改 \(f_{u,1}\) 和 \(f_{u,2}\) ,一次修改可以转化为一次矩阵乘法:
这样,可以在线段树上打 tag (与题目中的 tag 无关)记录子树修改次数,每次修改的时候乘若干次矩阵即可。矩阵的幂可以预处理。
代码
代码是很久以前写的,里面 f1 和 f2 和上面的定义是相反的,凑活看吧 ……
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
using namespace std;
namespace zyt
{
template<typename T>
inline bool read(T &x)
{
char c;
bool f = false;
x = 0;
do
c = getchar();
while (c != EOF && c != '-' && !isdigit(c));
if (c == EOF)
return false;
if (c == '-')
f = true, c = getchar();
do
x = x * 10 + c - '0', c = getchar();
while (isdigit(c));
if (f)
x = -x;
return true;
}
template<typename T>
inline void write(T x)
{
static char buf[20];
char *pos = buf;
if (x < 0)
putchar('-'), x = -x;
do
*pos++ = x % 10 + '0';
while (x /= 10);
while (pos > buf)
putchar(*--pos);
}
typedef long long ll;
const int N = 1e5 + 10, P = 998244353;
int n, inv2;
int power(int a, int b)
{
int ans = 1;
while (b)
{
if (b & 1)
ans = (ll)ans * a % P;
a = (ll)a * a % P;
b >>= 1;
}
return ans;
}
int inv(const int a)
{
return power(a, P - 2);
}
class Matrix
{
private:
int n, m;
public:
int data[2][2];
Matrix(const int _n = 0, const int _m = 0)
: n(_n), m(_m)
{
for (int i = 0; i < n; i++)
memset(data[i], 0, sizeof(int[m]));
}
Matrix operator * (const Matrix &b) const
{
Matrix ans(n, b.m);
for (int i = 0; i < n; i++)
for (int k = 0; k < m; k++)
for (int j = 0; j < b.n; j++)
ans.data[i][j] = (ans.data[i][j] + (ll)data[i][k] * b.data[k][j]) % P;
return ans;
}
}trans[N];
namespace Segment_Tree
{
struct node
{
int f0, f1, f2, tag, sumf0;
}tree[N << 2];
void update(const int rot)
{
tree[rot].sumf0 = tree[rot].f0;
if ((rot << 1 | 1) < (N << 2))
tree[rot].sumf0 = ((ll)tree[rot].sumf0 + tree[rot << 1].sumf0 + tree[rot << 1 | 1].sumf0) % P;
}
void pushdown(const int rot)
{
if (tree[rot].tag)
{
Matrix tmp(1, 2);
tree[rot << 1].tag += tree[rot].tag;
tmp.data[0][0] = tree[rot << 1].f1, tmp.data[0][1] = tree[rot << 1].f2;
tmp = tmp * trans[tree[rot].tag];
tree[rot << 1].f1 = tmp.data[0][0], tree[rot << 1].f2 = tmp.data[0][1];
tree[rot << 1 | 1].tag += tree[rot].tag;
tmp.data[0][0] = tree[rot << 1 | 1].f1, tmp.data[0][1] = tree[rot << 1 | 1].f2;
tmp = tmp * trans[tree[rot].tag];
tree[rot << 1 | 1].f1 = tmp.data[0][0], tree[rot << 1 | 1].f2 = tmp.data[0][1];
tree[rot].tag = 0;
}
}
void build(const int rot, const int lt, const int rt)
{
tree[rot].f1 = 1;
tree[rot].f0 = tree[rot].f2 = tree[rot].tag = tree[rot].sumf0 = 0;
if (lt == rt)
return;
int mid = (lt + rt) >> 1;
build(rot << 1, lt, mid), build(rot << 1 | 1, mid + 1, rt);
}
void mdf(const int rot, const int lt, const int rt, const int ls, const int rs)
{
if (ls <= lt && rt <= rs)
{
int f0 = tree[rot].f0, f1 = tree[rot].f1, f2 = tree[rot].f2;
tree[rot].f0 = ((ll)f0 + (ll)f1 * inv2 + (ll)f2 * inv2) % P;
tree[rot].f1 = (ll)f1 * inv2 % P;
tree[rot].f2 = (ll)f2 * inv2 % P;
++tree[rot].tag;
update(rot);
return;
}
int f0 = tree[rot].f0, f1 = tree[rot].f1, f2 = tree[rot].f2;
tree[rot].f0 = (ll)f0 * inv2 % P;
tree[rot].f1 = ((ll)f0 * inv2 + f1 + (ll)f2 * inv2) % P;
tree[rot].f2 = (ll)f2 * inv2 % P;
pushdown(rot);
int mid = (lt + rt) >> 1;
if (rs <= mid)
{
int f0 = tree[rot << 1 | 1].f0, f1 = tree[rot << 1 | 1].f1, f2 = tree[rot << 1 | 1].f2;
tree[rot << 1 | 1].f0 = (f0 + (ll)f2 * inv2) % P;
tree[rot << 1 | 1].f1 = f1;
tree[rot << 1 | 1].f2 = (ll)f2 * inv2 % P;
update(rot << 1 | 1);
mdf(rot << 1, lt, mid, ls, rs);
}
else if (ls > mid)
{
int f0 = tree[rot << 1].f0, f1 = tree[rot << 1].f1, f2 = tree[rot << 1].f2;
tree[rot << 1].f0 = (f0 + (ll)f2 * inv2) % P;
tree[rot << 1].f1 = f1;
tree[rot << 1].f2 = (ll)f2 * inv2 % P;
update(rot << 1);
mdf(rot << 1 | 1, mid + 1, rt, ls, rs);
}
else
{
mdf(rot << 1, lt, mid, ls, rs);
mdf(rot << 1 | 1, mid + 1, rt, ls, rs);
}
update(rot);
}
}
int work()
{
using namespace Segment_Tree;
int n, m, tmp = 1;
inv2 = inv(2);
read(n), read(m);
Matrix A(2, 2);
A.data[0][0] = A.data[0][1] = inv2;
A.data[1][1] = 1;
trans[0] = Matrix(2, 2);
trans[0].data[0][0] = trans[0].data[1][1] = 1;
for (int i = 1; i <= m; i++)
trans[i] = trans[i - 1] * A;
Segment_Tree::build(1, 1, n);
while (m--)
{
int opt;
read(opt);
if (opt == 1)
{
int l, r;
read(l), read(r);
Segment_Tree::mdf(1, 1, n, l, r);
tmp = tmp * 2LL % P;
}
else
write((ll)Segment_Tree::tree[1].sumf0 * tmp % P), putchar('\n');
}
return 0;
}
}
int main()
{
return zyt::work();
}