CEOI 2019 Day2 T2 魔法树 Magic Tree (LOJ#3166、CF1993B、and JOI2021 3.20 T3) (启发式合并平衡树,线段树合并)
前言
已经是第三次遇到原题。
第一次是在
J
O
I
2021
S
p
r
i
n
g
C
a
m
p
\rm JOI2021~Spring~Camp
JOI2021 Spring Camp 里遇到的类似的题(Food Court),我当初就用的启发式合并平衡树做法,但是由于常数不够优,没能通过
2
e
5
\tt2e5
2e5 的测试点。当时就只能用线段树合并做,
O
(
n
log
n
)
\rm O(n\log n)
O(nlogn) ,但是我不会。
第二次是在打
C
E
O
I
2019
D
a
y
2
\tt CEOI2019~Day2
CEOI2019 Day2 的虚拟赛时,遇到了这一道明显变简单许多(不再是基环树、且
n
≤
1
e
5
\rm n\leq1e5
n≤1e5)的原题,喜出望外,不管
T
1
\tt T1
T1 了,直接把它做了。果然,启发式合并平衡树又可以了。
这次是在 WK 讲题过程中,PPT 跳出了这么一道原题,直接导致提前下课……
题面
和笔者原先这篇博客做法几乎完全一致,这里就不重复了,下面是此题的平衡树做法代码:
CODE #1
#include<set>
#include<queue>
#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 100005
#define ENDL putchar('\n')
#define LL long long
#define DB double
#define lowbit(x) ((-x) & (x))
LL read() {
LL f = 1,x = 0;char s = getchar();
while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
return f * x;
}
int n,m,i,j,s,o,k;
//---------------------------------------------Treap
struct np{int s[2];np(){s[0]=s[1]=0;}np(int A,int B){s[0]=A;s[1]=B;}};
struct tr{
int s[2],siz,hp,ky;
LL nm,mx,lz;
tr(){s[0]=s[1]=siz=hp=0;ky=0;nm=mx=lz=0;}
}tre[MAXN];
int CNT;
int newnode(int tm,LL nm) {
int x = ++ CNT; tre[x]=tr();
tre[x].siz = 1; tre[x].hp = rand() *1145136223ll % 998244353;
tre[x].ky = tm; tre[x].nm = tre[x].mx = nm; return x;
}
int update(int x) {
if(!x) return x;
int ls = tre[x].s[0],rs = tre[x].s[1]; tre[x].siz = tre[ls].siz + tre[rs].siz + 1;
tre[x].mx = max(tre[x].nm,max(tre[ls].mx,tre[rs].mx) + tre[x].lz);
tre[0] = tr();return x;
}
void addnm(int x,LL y) {if(!x)return;tre[x].nm+=y;tre[x].lz+=y;tre[x].mx+=y;return ;}
void pushdown(int x) {
if(!x) return;
int ls = tre[x].s[0],rs = tre[x].s[1];
if(tre[x].lz) {
addnm(ls,tre[x].lz); addnm(rs,tre[x].lz);
tre[x].lz = 0;
}return ;
}
np spli(int x,int key) {
np as(0,0); if(!x) return as;
pushdown(x);
int d = (tre[x].ky <= key ? 1:0);
as = spli(tre[x].s[d],key);
tre[x].s[d] = as.s[d^1];
as.s[d^1] = update(x);
return as;
}
int merg(int p1,int p2) {
if(!p1 || !p2) return p1+p2;
pushdown(p1); pushdown(p2);
if(tre[p1].hp < tre[p2].hp) {
tre[p1].s[1] = merg(tre[p1].s[1],p2);
return update(p1);
}
tre[p2].s[0] = merg(p1,tre[p2].s[0]);
return update(p2);
}
int ins(int x,int y) {
np p = spli(x,tre[y].ky);
return merg(p.s[0],merg(y,p.s[1]));
}
int st[MAXN],cns;
void distr(int x) {
if(!x) return ; pushdown(x);
distr(tre[x].s[0]);st[++ cns]=x;distr(tre[x].s[1]);
tre[x].s[0]=tre[x].s[1]=0;update(x);return ;
}
void print(int x) {
if(!x) return ; pushdown(x);
print(tre[x].s[0]); printf(" (%d,%lld)",tre[x].ky,tre[x].nm);
print(tre[x].s[1]); return ;
}
//--------------------------------------------------------------
LL adn[MAXN];
int bing(int a,int b) {
if(tre[a].siz < tre[b].siz) swap(a,b);
cns = 0; distr(b);LL mxb = 0;
for(int i = 1;i <= cns;i ++) {
int y = st[i];
LL nb = max(mxb,tre[y].mx);
adn[i] = nb-mxb; mxb = nb;
np p = spli(a,tre[y].ky);
addnm(y,tre[p.s[0]].mx);
a = merg(p.s[0],p.s[1]);
}
for(int i = 1;i <= cns;i ++) {
int y = st[i];
np p = spli(a,tre[y].ky);
addnm(p.s[1],adn[i]);
a = merg(p.s[0],merg(y,p.s[1]));
}
return a;
}
int addtp(int a,int y) {
np p = spli(a,tre[y].ky);
addnm(y,tre[p.s[0]].mx);
return merg(p.s[0],merg(y,p.s[1]));
}
vector<int> g[MAXN];
int tm[MAXN],mg[MAXN];
int d[MAXN],rt[MAXN];
void dfs(int x) {
rt[x] = 0;
for(int i = 0;i < (int)g[x].size();i ++) {
int y = g[x][i];
dfs(y);
rt[x] = bing(rt[x],rt[y]);
}
rt[x] = addtp(rt[x],newnode(tm[x],mg[x]));
// printf("%d:\n",x);
// print(rt[x]);ENDL;
return ;
}
int main() {
n = read(); m = read(); k = read();
for(int i = 2;i <= n;i ++) {
s = read(); g[s].push_back(i);
}
for(int i = 1;i <= n;i ++) tm[i] = k,mg[i] = 0;
LL sm = 0;bool flag2 = 0;
for(int i = 1;i <= m;i ++) {
s = read();o = read();k = read();
tm[s] = o; mg[s] = k; sm += k;
if((int)g[s].size() > 0) flag2 = 0;
}
if(flag2) {
printf("%lld\n",sm);
return 0;
}
dfs(1);
printf("%lld\n",tre[rt[1]].mx);
return 0;
}
在看了别人的 DP 分析后,我发现我就是个大傻·逼
这题的合并操作其实并不是很复杂,如果把每个值都打在数轴上,是可以进行线段树合并的。在加入了自己这个值后,可以先暂时不取前缀最大值,而是在合并线段树的时候取!
线段树合并的复杂度是
O
(
n
log
n
)
\rm O(n\log n)
O(nlogn) ,少了不少,常数也更优:
CODE #2
H
a
n
d
I
n
D
e
v
i
l
\tt HandInDevil
HandInDevil 的全指针美观代码
// 指针,是美的!—— HandInDevil
#include<bits/stdc++.h>
using namespace std;
# define ll long long
# define read read1<ll>()
# define Type template<typename T>
Type T read1(){
T t=0;
char k;
bool vis=0;
do (k=getchar())=='-'&&(vis=1);while('0'>k||k>'9');
while('0'<=k&&k<='9')t=(t<<3)+(t<<1)+(k^'0'),k=getchar();
return vis?-t:t;
}
# define fre(k) freopen(k".in","r",stdin);freopen(k".out","w",stdout)
# define A pair<int,ll>
int s,m,k,d[100005],w[100005];
vector<int>G[100005];
struct node{
node *l,*r;
ll m,i,d;//max min
node(){l=r=NULL;m=i=d=0;}
}*a[100005];
bool Same(node *x){return !x->l&&!x->r;}
void Lz(node *x){
if(!x||!x->d)return;
x->m+=x->d;x->i+=x->d;
if(x->l)x->l->d+=x->d;
if(x->r)x->r->d+=x->d;
x->d=0;
}
node* Merge(node *x,node *y){
Lz(x);Lz(y);
if(!x||!y)return x?x:y;
if(Same(y)){
x->d+=y->m;
Lz(x);
delete[] y;
return x;
}if(Same(x))return Merge(y,x);
x->l=Merge(x->l,y->l);
x->r=Merge(x->r,y->r);
x->m=max(x->l->m,x->r->m);
x->i=min(x->l->i,x->r->i);
if(x->m==x->i){
delete x->l;x->l=NULL;
delete x->r;x->r=NULL;
}delete[] y;
return x;
}
ll query(node *x,int w,int l,int r){
if(!x)return 0;Lz(x);
if(Same(x))return x->m;
int mid=l+r>>1;
if(w<=mid)return query(x->l,w,l,mid);
return query(x->r,w,mid+1,r);
}void era(node *x){
if(!x)return;
era(x->l);era(x->r);
delete[] x;
}
void Turn(node *&x,int l,int r,ll v,int tl,int tr){
Lz(x);
if(x->i>=v)return;
if(x->m<=v&&l==tl&&r==tr){
x->m=x->i=v;era(x->l);era(x->r);
x->l=x->r=NULL;
return;
}if(Same(x)){
x->l=new node;x->r=new node;
x->l->m=x->r->m=x->l->i=x->r->i=x->m;
}
int mid=tl+tr>>1;
if(r<=mid)Turn(x->l,l,r,v,tl,mid);
else if(mid<l)Turn(x->r,l,r,v,mid+1,tr);
else Turn(x->l,l,mid,v,tl,mid),Turn(x->r,mid+1,r,v,mid+1,tr);
x->m=max(x->l->m,x->r->m);
x->i=min(x->l->i,x->r->i);
if(x->m==x->i){
delete x->l;x->l=NULL;
delete x->r;x->r=NULL;
}
}
void pr(node *x,int l=1,int r=k){
if(!x)return;Lz(x);
if(Same(x))printf("%d %d %lld\n",l,r,x->m);
pr(x->l,l,l+r>>1);pr(x->r,(l+r>>1)+1,r);
}
void dfs(int n){
a[n]=new node;
for(int i=0;i<G[n].size();++i)
if(G[n][i]){
dfs(G[n][i]);
a[n]=Merge(a[n],a[G[n][i]]);
}
if(d[n])Turn(a[n],d[n],k,w[n]+query(a[n],d[n],1,k),1,k);
// cout<<"::"<<n<<endl;pr(a[n]);
}
int main(){
s=read,m=read,k=read;
for(int i=2;i<=s;++i)G[read].push_back(i);
for(int i=1;i<=m;++i){
int x=read;
d[x]=read;w[x]=read;
}dfs(1);
cout<<query(a[1],k,1,k);
return 0;
}
CEOI 2019 Day2 T2 魔法树 Magic Tree (LOJ#3166、CF1993B、and JOI2021 3.20 T3) (启发式合并平衡树,线段树合并)的更多相关文章
- [BZOJ 2212] [Poi2011] Tree Rotations 【线段树合并】
题目链接:BZOJ - 2212 题目分析 子树 x 内的逆序对个数为 :x 左子树内的逆序对个数 + x 右子树内的逆序对个数 + 跨越 x 左子树与右子树的逆序对. 左右子树内部的逆序对与是否交换 ...
- BZOJ_2212_[Poi2011]Tree Rotations_线段树合并
BZOJ_2212_[Poi2011]Tree Rotations_线段树合并 Description Byteasar the gardener is growing a rare tree cal ...
- 洛谷P4482 [BJWC2018]Border 的四种求法 字符串,SAM,线段树合并,线段树,树链剖分,DSU on Tree
原文链接https://www.cnblogs.com/zhouzhendong/p/LuoguP4482.html 题意 给定一个字符串 S,有 q 次询问,每次给定两个数 L,R ,求 S[L.. ...
- BZOJ.3307.雨天的尾巴(dsu on tree/线段树合并)
BZOJ 洛谷 \(dsu\ on\ tree\).(线段树合并的做法也挺显然不写了) 如果没写过\(dsu\)可以看这里. 对修改操作做一下差分放到对应点上,就成了求每个点子树内出现次数最多的颜色, ...
- [NOIP2016 DAY1 T2]天天爱跑步-[差分+线段树合并][解题报告]
[NOIP2016 DAY1 T2]天天爱跑步 题面: B[NOIP2016 DAY1]天天爱跑步 时间限制 : - MS 空间限制 : 565536 KB 评测说明 : 2s Description ...
- 【BZOJ2212】[Poi2011]Tree Rotations 线段树合并
[BZOJ2212][Poi2011]Tree Rotations Description Byteasar the gardener is growing a rare tree called Ro ...
- 【bzoj2212】[Poi2011]Tree Rotations 权值线段树合并
原文地址:http://www.cnblogs.com/GXZlegend/p/6826614.html 题目描述 Byteasar the gardener is growing a rare tr ...
- bzoj3307雨天的尾巴(权值线段树合并/DSU on tree)
题目大意: 一颗树,想要在树链上添加同一物品,问最后每个点上哪个物品最多. 解题思路: 1.线段树合并 假如说物品数量少到可以暴力添加,且树点极少,我们怎么做. 首先在一个树节点上标记出哪些物品有多少 ...
- CF600E Lomsat gelral——线段树合并/dsu on tree
题目描述 一棵树有$n$个结点,每个结点都是一种颜色,每个颜色有一个编号,求树中每个子树的最多的颜色编号的和. 这个题意是真的窒息...具体意思是说,每个节点有一个颜色,你要找的是每个子树中颜色的众数 ...
随机推荐
- 第6章 字符串(上)——C风格字符串
6.1 C-strings(C 风格字符串) C风格字符串: 字符数组是元素为字符型的数组,字符串是以空字符'\0' 作为数组最后一个元素的字符数组. 如果指定了数组的大小,而字符串的长度又小于数组大 ...
- c++ 超大整数除法 高精度除法
c++ 超大整数除法 高精度除法 解题思路 计算a/b,其中a为大整数,b为普通整数,商为c,余数为r. 根据手算除法的规则,上一步的余数记为r,则本次计算的被除数为t=r*10+被除数的本位数值a[ ...
- MongoDB学习总览
第1部分: MongoDB入门(第1~6章) 该部分介绍MongoDB的基本概念及入门知识. 通过该部分的学习,读者可对MongoDB自身的技术全貌形成一定的认识. 第2部分: MongoDB微服务开 ...
- 基于MybatisPlus代码生成器(2.0新版本)
一.模块简介 1.功能亮点 实时读取库表结构元数据信息,比如表名.字段名.字段类型.注释等,选中修改后的表,点击一键生成,代码成即可提现出表结构的变化. 单表快速转化restful风格的API接口并对 ...
- Spring Security认证器实现
目录 拦截请求 验证过程 返回完整的Authentication 收尾工作 结论 一些权限框架一般都包含认证器和决策器,前者处理登陆验证,后者处理访问资源的控制 Spring Security的登陆请 ...
- Linux 磁盘挂载和swap空间管理
挂载:把指定的设备和根下面的某个文件夹建立关联 卸载:解除两者关系的过程 挂载文件系统:mount 格式:mount device mountpoint --- mount 设备名 挂载点 mount ...
- go语言学习笔记-初识Go语言
Go语言是怎样诞生的? Go语言的创始人有三位,分别是图灵奖获得者.C语法联合发明人.Unix之父肯·汤普森(Ken Thompson).Plan 9操作系统领导者.UTF-8编码的最初设计者罗伯·派 ...
- 集合-新特性foreach循环遍历集合或项目
1.增强for循环对集合的遍历 点击查看代码 @Test //集合遍历 public void test3(){ Collection coll = new ArrayList(); coll.add ...
- 浅学hello world
Hello world 1.随便新建一个文件夹,存放代码 2.新建一个java文件 .后缀名为.java .Hello.java .[注意点]系统没显示后缀名的可以自己手动打开 3.编写代码 publ ...
- Re:用webpack从零开始的vue-cli搭建'生活'
有了vue-cli的帮助,我们创建vue的项目非常的方便,使用vue create然后选择些需要的配置项就能自动帮我们创建配置好的webpack项目脚手架了,实在是'居家旅行'必备良药.这次借着学习w ...