@description@

九条可怜是一个喜欢数据结构的女孩子,在常见的数据结构中,可怜最喜欢的就是线段树。

线段树的核心是懒标记,下面是一个带懒标记的线段树的伪代码,其中 tag​ 数组为懒标记:

其中函数 \(Lson(Node)\) 表示 \(Node\) 的左儿子,\(Rson(Node)\) 表示 \(Node\) 的右儿子。

现在可怜手上有一棵 \([1,n]\) 上的线段树,编号为 \(1\)。这棵线段树上的所有节点的 tag​ 均为 \(0\)。接下来可怜进行了 \(m\) 次操作,操作有两种:

  • \(1\ l\ r\),假设可怜当前手上有 \(t\) 棵线段树,可怜会把每棵线段树复制两份(tag 数组也一起复制),原先编号为 \(i\) 的线段树复制得到的两棵编号为 \(2i-1\) 与 \(2i\),在复制结束后,可怜手上一共有 \(2t\) 棵线段树。接着,可怜会对所有编号为奇数的线段树进行一次 \(\rm{Modify}(root,1,n,l,r)\)。
  • \(2\),可怜定义一棵线段树的权值为它上面有多少个节点 tag 为 \(1\)。可怜想要知道她手上所有线段树的权值和是多少。

原题传送门。

@solution@

考虑每个结点的贡献。

观察伪代码,修改操作 [l, r] 导致结点 x 的 tag​ 可能改变的情况只有 3 种:

(1)[l, r] 不包含 x 的父结点,但包含 x。此时 x 的 tag 强制变为 1。

(2)[l, r] 不包含 x,但与 x 有交。此时 x 的 tag 强制变为 0。

(3)[l, r] 与 x 没有交,但与 x 的父结点有交。此时如果 x 的某祖先有 tag,则 x 也有 tag。

因此考虑 dp:定义 dp(0/1, 0/1, x) 表示 x 的祖先结点是否有 tag,x 本身是否有 tag,这 4 种情况对应的方案数。

可以做到 \(O(nm)\) 的 dp,获得 40 分的好成绩。

考虑优化。观察 dp 的转移,发现只有线段树上单点修改/子树修改,单点询问/子树询问。

直接维护一下线段树上的单点 dp值/转移矩阵 与子树 dp值/转移矩阵 即可。

可以把 \(4\times 4\) 的转移矩阵优化成 3 个元素的转移矩阵,这样会跑得快一些。

这样就可以 \(O(m\log n)\) 通过该题。

@accepted code@

#include <cstdio>
#include <algorithm>
using namespace std; #define lch (x << 1)
#define rch (x << 1 | 1)
#define rep(i, x, n) for(int i=x;i<n;i++) const int MAXN = 100000;
const int MOD = 998244353; inline int add(int x, int y) {x += y; return x >= MOD ? x - MOD : x;}
inline int sub(int x, int y) {x -= y; return x < 0 ? x + MOD : x;}
inline int mul(int x, int y) {return (int)(1LL * x * y % MOD);} namespace segtree{
struct tag{
int t00, t01, t11;
friend tag operator * (const tag &a, const tag &b) {
tag c;
c.t00 = mul(a.t00, b.t00);
c.t01 = add(mul(a.t00, b.t01), mul(a.t01, b.t11));
c.t11 = mul(a.t11, b.t11);
return c;
}
};
struct state{
int f[2][2];
friend state operator * (const state &a, const tag &b) {
state c;
rep(i, 0, 2) {
c.f[0][i] = mul(b.t00, a.f[0][i]);
c.f[1][i] = add(mul(b.t11, a.f[1][i]), mul(b.t01, a.f[0][i]));
}
return c;
}
friend state operator + (const state &a, const state &b) {
state c;
rep(i, 0, 2) rep(j, 0, 2)
c.f[i][j] = add(a.f[i][j], b.f[i][j]);
return c;
}
}; int le[8*MAXN + 5], ri[8*MAXN + 5], ans;
state s[8*MAXN + 5], v[8*MAXN + 5]; tag tg[8*MAXN + 5]; bool vis[8*MAXN + 5];
void pushup(int x) {
if( le[x] == ri[x] ) s[x] = v[x];
else s[x] = s[lch] + s[rch] + v[x];
}
void addtag(int x, tag k) {s[x] = s[x] * k, v[x] = v[x] * k, tg[x] = tg[x] * k, vis[x] = true;}
void pushdown(int x) {
if( !vis[x] ) return ;
addtag(lch, tg[x]), addtag(rch, tg[x]);
tg[x].t00 = tg[x].t11 = 1, tg[x].t01 = 0, vis[x] = false;
}
void build(int x, int l, int r) {
le[x] = l, ri[x] = r, v[x].f[0][0] = 1;
tg[x].t00 = tg[x].t11 = 1, tg[x].t01 = 0;
if( l != r ) {
int m = (l + r) >> 1;
build(lch, l, m), build(rch, m + 1, r);
}
pushup(x);
}
void modify_segment(int x, int type) {
if( le[x] == ri[x] ) return ;
pushdown(x);
int del = add(s[lch].f[0][1], s[lch].f[1][1]); ans = add(ans, del);
del = add(s[rch].f[0][1], s[rch].f[1][1]); ans = add(ans, del);
if( type == 1 )
addtag(lch, (tag){1, 1, 2}), addtag(rch, (tag){1, 1, 2});
else addtag(lch, (tag){2, 0, 2}), addtag(rch, (tag){2, 0, 2});
pushup(x);
}
void modify_point(int x, int type) {
if( type == 1 ) {
int del = add(add(v[x].f[0][1], v[x].f[1][1]), add(v[x].f[0][0], v[x].f[1][0]));
v[x].f[0][1] = add(v[x].f[0][1], del), ans = add(ans, del);
}
else if( type == 2 ) {
int del = add(add(v[x].f[0][1], v[x].f[1][1]), add(v[x].f[0][0], v[x].f[1][0]));
v[x].f[0][0] = add(v[x].f[0][0], del);
}
else if( type == 3 ) {
int del = add(add(v[x].f[0][1], v[x].f[1][0]), v[x].f[1][1]);
v[x].f[0][1] = add(v[x].f[0][1], del), ans = add(ans, del);
v[x].f[0][0] = add(v[x].f[0][0], v[x].f[0][0]);
}
pushup(x);
}
void update(int x, int l, int r) {
if( l <= le[x] && ri[x] <= r )
modify_point(x, 1), modify_segment(x, 1);
else {
modify_point(x, 2); int m = (le[x] + ri[x]) >> 1; pushdown(x);
if( r <= m )
update(lch, l, r), modify_point(rch, 3), modify_segment(rch, 2);
else if( l > m )
update(rch, l, r), modify_point(lch, 3), modify_segment(lch, 2);
else update(lch, l, r), update(rch, l, r);
pushup(x);
}
}
}; int main() {
int n, m; scanf("%d%d", &n, &m);
segtree::build(1, 1, n);
for(int i=1,op,l,r;i<=m;i++) {
scanf("%d", &op);
if( op == 1 )
scanf("%d%d", &l, &r), segtree::update(1, l, r);
else printf("%d\n", segtree::ans);
}
}

@details@

不要像我一样还写了另一棵线段树维护原线段树的dfs序然后变成了丑陋的O(nlog^2n)。

本题还有更优美的解法:观察到某个点是否有 tag 与它祖先上是否有 tag 相互独立,于是我们可以分别维护一个点是否有 tag 的概率与它祖先是否有 tag 的概率。

这个做法好像常数小很多。不管了反正我的做法也过了。

@loj - 3043@「ZJOI2019」线段树的更多相关文章

  1. 【LOJ】#3043. 「ZJOI2019」线段树

    LOJ#3043. 「ZJOI2019」线段树 计数转期望的一道好题-- 每个点设两个变量\(p,q\)表示这个点有\(p\)的概率有标记,有\(q\)的概率到祖先的路径上有个标记 被覆盖的点$0.5 ...

  2. Loj #2570. 「ZJOI2017」线段树

    Loj #2570. 「ZJOI2017」线段树 题目描述 线段树是九条可怜很喜欢的一个数据结构,它拥有着简单的结构.优秀的复杂度与强大的功能,因此可怜曾经花了很长时间研究线段树的一些性质. 最近可怜 ...

  3. 「ZJOI2019」线段树 解题报告

    「ZJOI2019」线段树 听说有人喷这个题简单,然后我就跑去做,然后自闭感++,rp++(雾) 理性分析一波,可以发现最后形成的\(2^k\)个线段树,对应的操作的一个子集,按时间顺序作用到这颗线段 ...

  4. LOJ 3043: 洛谷 P5280: 「ZJOI2019」线段树

    题目传送门:LOJ #3043. 题意简述: 你需要模拟线段树的懒标记过程. 初始时有一棵什么标记都没有的 \(n\) 阶线段树. 每次修改会把当前所有的线段树复制一份,然后对于这些线段树实行一次区间 ...

  5. LOJ#3043.【ZJOI2019】 线段树 线段树,概率期望

    原文链接www.cnblogs.com/zhouzhendong/p/ZJOI2019Day1T2.html 前言 在LOJ交了一下我的代码,发现它比选手机快将近 4 倍. 题解 对于线段树上每一个节 ...

  6. 「ZJOI2019」线段树

    传送门 Description 线段树的核心是懒标记,下面是一个带懒标记的线段树的伪代码,其中 tag 数组为懒标记: 其中函数\(Lson(Node)\)表示\(Node\)的左儿子,\(Rson( ...

  7. @loj - 2093@ 「ZJOI2016」线段树

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 小 Yuuka 遇到了一个题目:有一个序列 a1,a2,..., ...

  8. 【LOJ3043】「ZJOI2019」线段树

    题面 问题可以转化为每次区间覆盖操作有 \(\frac{1}{2}\) 的概率进行,求标记和的期望.于是我们只要求出所有点有标记的概率即可. 我们设 \(f_i\) 表示节点 \(i\) 有标记的概率 ...

  9. 「模板」 线段树——区间乘 && 区间加 && 区间求和

    「模板」 线段树--区间乘 && 区间加 && 区间求和 原来的代码太恶心了,重贴一遍. #include <cstdio> int n,m; long l ...

随机推荐

  1. C# 数据操作系列 - 13 SugarSql初探

    0. 前言 前言,暂时挥别NHibernate(虽然我突然发现这玩意还挺有意思的,不过看得人不多).大步进入了有很多小伙伴向我安利的SQLSugar,嗯,我一直叫SugarSQL,好像是这个吧? 这是 ...

  2. Spring Bean 定义

    Bean 定义 被称作 bean 的对象是构成应用程序的支柱.也是由 Spring IoC 容器管理的. bean 是一个被实例化,组装,并通过 Spring IoC 容器所管理的对象. 这些 bea ...

  3. POJ1930

    题目链接:http://poj.org/problem?id=1930 题目大意: 给一个无限循环小数(循环节不知),要求你输出当该小数所化成的最简分数分母最小时所对应的最简分数. AC思路: 完全没 ...

  4. 【解决办法】IIS环境中,打开网站后就直接列出了所有文件

    有时候访问一个不应当被访问的网站目录时网站会列出该目录下的所有文件,这很不安全,尤其是我们希望我们自己的网站不出现这种情况,如下图所示. 解决办法,在网站根目录新建web.config文件,内容如下: ...

  5. js常用 方法 封装

    // 监听滚动,用于列表页向下加载--------------------------------- function loadmore(callback) { $(window).scroll(fu ...

  6. 线程的 run() 和 start() ; 太骚了 ~~

    线程的 run() 和 start() ; 太骚了 ~~ 线程的 run() 和 start() ; 太骚了 ~~注:本文转载于:CodeCow · 程序牛 的个人博客:http://www.code ...

  7. [ES6系列-01]Class:面向对象的“新仇旧恨”

    [原创]CoderPower 大家好,这里是码路工人有力量,我是码路工人,你们是力量. 这是公众号(码路工人有力量)开通后的第二篇,写得还是有待改进吧.这次准备写一个关于ES6基础的短文系列,努力尽快 ...

  8. 安卓全屏或沉浸式状态栏下输入框(EditText)被键盘遮挡解决方法

    沉浸式状态栏用了一段时间了,一直没发现安卓在这方面的坑.最近在集成环信自定义UI的过程中,发现将环信界面设置为沉浸式之后最底部的消息输入框不随键盘弹起而变化了,一直显示在屏幕最下方,体验非常差. 后来 ...

  9. Centos8 删除了yum.repos.d 下面的文件

    原文: https://www.cnblogs.com/junjind/p/9016107.html centos-release-8.1-1.1911.0.9.el8.x86_64 找到 https ...

  10. VUE源码解析心得

    解读vue源码比较好奇的几个点: VUE MVVM 原理 http://www.cnblogs.com/guwei4037/p/5591183.html https://cn.vuejs.org/v2 ...