@loj - 3043@「ZJOI2019」线段树
@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」线段树的更多相关文章
- 【LOJ】#3043. 「ZJOI2019」线段树
LOJ#3043. 「ZJOI2019」线段树 计数转期望的一道好题-- 每个点设两个变量\(p,q\)表示这个点有\(p\)的概率有标记,有\(q\)的概率到祖先的路径上有个标记 被覆盖的点$0.5 ...
- Loj #2570. 「ZJOI2017」线段树
Loj #2570. 「ZJOI2017」线段树 题目描述 线段树是九条可怜很喜欢的一个数据结构,它拥有着简单的结构.优秀的复杂度与强大的功能,因此可怜曾经花了很长时间研究线段树的一些性质. 最近可怜 ...
- 「ZJOI2019」线段树 解题报告
「ZJOI2019」线段树 听说有人喷这个题简单,然后我就跑去做,然后自闭感++,rp++(雾) 理性分析一波,可以发现最后形成的\(2^k\)个线段树,对应的操作的一个子集,按时间顺序作用到这颗线段 ...
- LOJ 3043: 洛谷 P5280: 「ZJOI2019」线段树
题目传送门:LOJ #3043. 题意简述: 你需要模拟线段树的懒标记过程. 初始时有一棵什么标记都没有的 \(n\) 阶线段树. 每次修改会把当前所有的线段树复制一份,然后对于这些线段树实行一次区间 ...
- LOJ#3043.【ZJOI2019】 线段树 线段树,概率期望
原文链接www.cnblogs.com/zhouzhendong/p/ZJOI2019Day1T2.html 前言 在LOJ交了一下我的代码,发现它比选手机快将近 4 倍. 题解 对于线段树上每一个节 ...
- 「ZJOI2019」线段树
传送门 Description 线段树的核心是懒标记,下面是一个带懒标记的线段树的伪代码,其中 tag 数组为懒标记: 其中函数\(Lson(Node)\)表示\(Node\)的左儿子,\(Rson( ...
- @loj - 2093@ 「ZJOI2016」线段树
目录 @description@ @solution@ @accepted code@ @details@ @description@ 小 Yuuka 遇到了一个题目:有一个序列 a1,a2,..., ...
- 【LOJ3043】「ZJOI2019」线段树
题面 问题可以转化为每次区间覆盖操作有 \(\frac{1}{2}\) 的概率进行,求标记和的期望.于是我们只要求出所有点有标记的概率即可. 我们设 \(f_i\) 表示节点 \(i\) 有标记的概率 ...
- 「模板」 线段树——区间乘 && 区间加 && 区间求和
「模板」 线段树--区间乘 && 区间加 && 区间求和 原来的代码太恶心了,重贴一遍. #include <cstdio> int n,m; long l ...
随机推荐
- form和table的区别
转自:http://www.360doc.com/content/11/0108/14/5416196_84975265.shtml html中form表示一个表单,用来把一系列的控件包围起来,然后再 ...
- poj2778 AC自动机
以下内容均为转载,,只有代码是自己写的=-= http://blog.csdn.net/morgan_xww/article/details/7834801 转载地址 博主写的很好 ------- ...
- CentOS7初始化服务器开发环境——根据个人习惯而定
目录 修改hostname 创建个人账户和组 修改hostname 编辑主机名称,注意:执行以下指令,无需重启服务器,因为此指令实时写入linux 内核 hostnamectl --static se ...
- Android中的成员变量与局部变量
简单说一下吧, note:java中的成员变量就是c++中的全局变量 1.可以在全局范围内使用:局部变量只能在其定义的方法里使用. 2.成员变量可以不赋初值使用,调用时有系统的默认的初值,比如int类 ...
- Maven——pom.xml文件报错:Missing artifact:jar包
原因:该错误原因为maven库中jar包无法更新 解决方法:找到maven库中对应的jar包路径,删除文件夹中的红框中的三个文件
- wordpress中文章发布时间不显示?用get_the_date代替the_date
今天发现,在主题中部分地方使用the_date函数来显示文章发布的时间时,竟然发生不显示时间的情况,再仔细看了一下这些文章,有些都是经过几次修改和保存的,可能是由于the_date只是显示文章第一次发 ...
- Python面向对象的特征跟私有属性
面向对象的三大特征 继成.封装.多态: 继承:一个类可以继承一个或多个类,被继承的类叫做父类,继承的类叫做子类 封装:将数据和方法放在了一个类中就构成了封装 多态:指的一类事物有多重形态,一个抽象类 ...
- rancher证书过期
背景 无法打开rancher服务,报错如下截图,可以看出是证书过期了无法连上k8s,注意这里的证书是rancher自身证书并非k8s证书. 解决方法 rancher升级:https://rancher ...
- Linux—vim/vi 翻页跳转命令快捷键
以下组合若没有特殊说明,基本都是键位组合. vim翻页 vim翻半页 ctr-d:向后翻半页 ctr-u:向前翻半页 vim整整页 ctr+f:向后翻整页 ctr+b:向前翻整页 vim跳转 vim跳 ...
- 一个 static 还能难得住我?
static 是我们日常生活中经常用到的关键字,也是 Java 中非常重要的一个关键字,static 可以修饰变量.方法.做静态代码块.静态导包等,下面我们就来具体聊一聊这个关键字,我们先从基础开始, ...