[JZOJ6359] 【NOIP2019模拟2019.9.15】小ω的树
题目
题目大意
给你一棵树,带点权和边权。
要你选择一个联通子图,使得点权和乘最小边权最大。
支持修改点权操作。
思考历程
显然,最先想到的当然是重构树了……
重构树就是在做最大生成树的时候,当两个联通块相连时,新增一个点,将两个联通块的根节点连上去。
这个新建的点上记录这条边的边权,那么以它为子树的答案就是子树的点权和乘上自己表示的这条边的边权。
然后题目就变成了一个似乎很经典的问题:给你\(a_i\)和\(b_i\),每次修改可以将区间内的\(a_i\)区间加,询问最大的\(a_ib_i\)……
然而由于我智力低下,完全不会维护啊……
于是就暴力维护了。
正解
其实可以分块……
这可以看做一堆一次函数取最大值。
如果只是一条链,直接分成\(\sqrt n\)块,每个块里维护一个凸壳。
每个块有个\(tag\)标记,表示当前块内的答案\(ans\)是\(x=tag\)和块内的一次函数交点的纵坐标最大值。
那么修改的时候整块就直接改\(tag\),然后在凸壳上二分出\(ans\)。散块就暴力重构。
对于一棵树,当然是树链剖分了。设某条重链的长度为\(L\),则将重链分成\(\sqrt L\)块,然后一模一样地搞。
这样时间复杂度是否有保障呢?
首先,对于一条重链,它自己的时间复杂度就是\(\sqrt L \lg L\)
既然有好多条重链,是不是意味着再乘个\(\lg\)?
然而不是这样。
树链剖分有个性质:轻儿子的大小小于整棵树大小的一半。
所以从上到下,每个重链的链顶子树的大小是一直在减半的。
设子树大小为\(S\)。显然\(\sqrt L \lg L\leq \sqrt S \lg S\)
所以时间复杂度就是\(\sqrt S \lg S+\sqrt \frac{S}{2} \lg \frac{S}{2}+\sqrt \frac{S}{4} \lg \frac{S}{4}+...\)
\(\lg\)比较小,所以看成同样的。提出来,就变成了\(\sqrt S+\sqrt \frac{S}{2}+\sqrt \frac{S}{4}+...=O(\sqrt S)\)
所以单次操作时间复杂度居然是\(O(\sqrt n\lg n)\)!
所以这是能够过的……
不过记得要卡一卡常数。
另外有个强大的集训队大佬提供的方法:定期重构。
也就是做几个询问重构一次。
设做\(B\)个询问重构一次。
对于询问的点,建一棵虚树。显然虚树的点有\(2B-1\)个。建完之后对于虚树上的每一条边维护一个凸壳,记录一个\(tag\),求答案的时候在凸壳上二分。
计算询问的时候,直接暴力沿着边打\(tag\)。虚树的边有\(2B-2\)个,所以一次修改时间就是\(O(B \lg n)\)
所以总的时间就是:\(O(n\frac{m}{B}\lg n+mB\lg n)\)。
平衡规划得\(B=\sqrt n\)
代码
树链剖分:
using namespace std;
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define N 300010
#define ll long long
inline int input(){
char ch=getchar();
while (ch<'0' || '9'<ch)
ch=getchar();
int x=0;
do{
x=x*10+ch-'0';
ch=getchar();
}
while ('0'<=ch && ch<='9');
return x;
}
int n,m;
int a[N];
struct edge{
int x,y;
ll v;
} ed[N];
inline bool cmped(const edge &a,const edge &b){return a.v>b.v;}
int cnt,fa[N*2];
int ufs[N*2];
inline int get(int x){
if (ufs[x]==x)
return x;
return ufs[x]=get(ufs[x]);
}
struct EDGE{
int to;
EDGE *las;
} e[N*2];
int ne;
EDGE *last[N*2];
ll sum[N*2],mn[N*2],p[N*2],q[N*2];
int siz[N*2],hs[N*2],top[N*2],dfn[N*2],nowdfn,lis[N*2],len[N*2];
void dfs1(int x){
siz[x]=1;
for (EDGE *ei=last[x];ei;ei=ei->las){
dfs1(ei->to);
sum[x]+=sum[ei->to];
siz[x]+=siz[ei->to];
if (siz[ei->to]>siz[hs[x]])
hs[x]=ei->to;
}
}
void dfs2(int x,int t){
top[x]=t;
len[top[x]]++;
dfn[x]=++nowdfn;
lis[nowdfn]=x;
if (hs[x])
dfs2(hs[x],t);
for (EDGE *ei=last[x];ei;ei=ei->las)
if (ei->to!=hs[x])
dfs2(ei->to,ei->to);
}
int bel[N*2],nb,L[N*2],R[N*2];
int st[N*2],*h[N*2],*t[N*2];
ll tag[N*2];
ll ans[N*2];
ll h1[20000000],h2[20000000],nh1,nh2;
inline void recover(int b){
for (int i=L[b];i<=R[b];++i)
q[lis[i]]+=p[lis[i]]*tag[b];
tag[b]=0;
}
int tmp[N*2];
inline bool cmpt(int a,int b){return p[a]<p[b] || p[a]==p[b] && q[a]>q[b];}
inline bool ok(int a,int b,int x){return 0>=x*(p[a]-p[b])+q[a]-q[b];}
inline void getans(int b){
int *l=h[b]+1,*r=t[b],*res=h[b];
while (l<=r){
int *mid=l+(r-l>>1);
if (ok(*(mid-1),*mid,tag[b]))
l=(res=mid)+1;
else
r=mid-1;
}
ans[b]=p[*res]*tag[b]+q[*res];
}
inline bool judge(int i,int j,int k){
return (q[k]-q[j])*(p[j]-p[i])>=(q[i]-q[j])*(p[j]-p[k]);
}
inline void build(int b){
int k=0;
for (int i=L[b];i<=R[b];++i)
tmp[k++]=lis[i];
sort(tmp,tmp+k,cmpt);
*h[b]=tmp[0];
t[b]=h[b];
for (int i=1;i<k;++i)
if (p[*t[b]]!=p[tmp[i]]){
while (h[b]<t[b] && judge(*(t[b]-1),*t[b],tmp[i]))
t[b]--;
*(++t[b])=tmp[i];
}
getans(b);
}
inline void add(int l,int r,int c){
if (bel[l]==bel[r]){
recover(bel[l]);
for (int i=l;i<=r;++i)
q[lis[i]]+=p[lis[i]]*c;
h2[nh2++]=ans[bel[l]];
push_heap(h2,h2+nh2);
build(bel[l]);
h1[nh1++]=ans[bel[l]];
push_heap(h1,h1+nh1);
return;
}
recover(bel[r]);
for (int i=L[bel[r]];i<=r;++i)
q[lis[i]]+=p[lis[i]]*c;
h2[nh2++]=ans[bel[r]];
push_heap(h2,h2+nh2);
build(bel[r]);
h1[nh1++]=ans[bel[r]];
push_heap(h1,h1+nh1);
for (int i=bel[l];i<=bel[r]-1;++i){
h2[nh2++]=ans[i];
push_heap(h2,h2+nh2);
tag[i]+=c;
getans(i);
h1[nh1++]=ans[i];
push_heap(h1,h1+nh1);
}
}
inline void change(int x,int c){
for (;x;x=fa[top[x]])
add(dfn[top[x]],dfn[x],c);
}
int main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
n=input(),m=input();
for (int i=1;i<=n;++i)
a[i]=input();
for (int i=1;i<n;++i)
ed[i]={input(),input(),input()};
sort(ed+1,ed+n,cmped);
for (int i=1;i<=n;++i)
ufs[i]=i;
cnt=n;
for (int i=1;i<n;++i){
int xx=get(ed[i].x),yy=get(ed[i].y);
++cnt;
ufs[xx]=ufs[yy]=ufs[cnt]=cnt;
fa[xx]=fa[yy]=cnt;
mn[cnt]=ed[i].v;
}
for (int i=1;i<cnt;++i){
e[ne]={i,last[fa[i]]};
last[fa[i]]=e+ne++;
}
for (int i=1;i<=n;++i)
sum[i]=a[i];
dfs1(cnt),dfs2(cnt,cnt);
for (int i=1;i<=cnt;++i)
p[i]=mn[i],q[i]=sum[i]*mn[i];
for (int i=1;i<=cnt;++i)
if (top[i]==i){
int B=sqrt(len[i]);
for (int j=0;j*B<len[i];++j){
nb++;
for (int k=0;k<B && j*B+k<len[i];++k)
bel[dfn[i]+j*B+k]=nb;
L[nb]=dfn[i]+j*B;
R[nb]=dfn[i]+min(j*B+B-1,len[i]-1);
h[nb]=&st[L[nb]];
build(nb);
h1[nh1++]=ans[nb];
push_heap(h1,h1+nh1);
}
}
while (m--){
int x=input(),v=input(),c=v-a[x];
a[x]=v;
change(x,c);
while (h1[0]==h2[0]){
pop_heap(h1,h1+nh1--);
pop_heap(h2,h2+nh2--);
}
printf("%lld\n",h1[0]);
}
return 0;
}
总结
分块大法好啊……
[JZOJ6359] 【NOIP2019模拟2019.9.15】小ω的树的更多相关文章
- 6359. 【NOIP2019模拟2019.9.15】小ω的树(tree)(定期重构)
题目描述 题解 qy的毒瘤题 CSP搞这种码农题当场手撕出题人 先按照边权从大到小建重构树,然后40%暴力修改+查找即可 100%可以定期重构+平衡规划,每次把B个询问拉出来建虚树,在虚树上暴力维护每 ...
- 6358. 【NOIP2019模拟2019.9.15】小ω的仙人掌
题目 题目大意 给你一串二元组\((a_i,b_i)\)的数列. 求最小的区间\([l,r]\)长度,满足\([l,r]\)中的每个二元组选或不选,使得\(\sum a_i=w\)且\(\sum b_ ...
- 6368. 【NOIP2019模拟2019.9.25】质树
题目 题目大意 有个二叉树,满足每个点跟它的所有祖先互质. 给出二叉树的中序遍历的点权,还原一种可能的方案. 思考历程 首先想到的当然是找到一个跟全部互质的点作为根,然后左右两边递归下去处理-- 然而 ...
- 6389. 【NOIP2019模拟2019.10.26】小w学图论
题目描述 题解 之前做过一次 假设图建好了,设g[i]表示i->j(i<j)的个数 那么ans=∏(n-g[i]),因为连出去的必定会构成一个完全图,颜色互不相同 从n~1染色,点i的方案 ...
- 6380. 【NOIP2019模拟2019.10.06】小w与最长路(path)
题目 题目大意 给你一棵树,对于每一条边,求删去这条边之后,再用一条边(自己定)连接两个连通块,形成的树的直径最小是多少. 正解 首先,将这棵树的直径给找出来.显然,如果删去的边不在直径上,那么答案就 ...
- 6424. 【NOIP2019模拟2019.11.13】我的订书机之恋
题目描述 Description Input Output Sample Input 见下载 Sample Output 见下载 Data Constraint 题解 lj题卡线段树 求出每个右端点往 ...
- 6407. 【NOIP2019模拟11.05】小 D 与随机
题目描述 Description Input 第一行两个个整数 n,k. 之后 n -1 行,第 i 行两个整数 ui, vi, 表示一条树边. 保证输入的数据构成一棵树. Output 一行一个数表 ...
- 6392. 【NOIP2019模拟2019.10.26】僵尸
题目描述 题解 吼题但题解怎么这么迷 考虑一种和题解不同的做法(理解) 先把僵尸离散化,h相同的钦(ying)点一个大小 (可以发现这样每种情况只会被算正好一次) 计算完全被占领的方案,然后1-方案/ ...
- 6364. 【NOIP2019模拟2019.9.20】养马
题目描述 题解 一种显然的水法:max(0,-(点权-边权之和*2)) 这样会挂是因为在中途体力值可能会更小,所以考虑求走完每棵子树所需的至少体力值 考虑从子树往上推求出当前点的答案 设每棵子树从根往 ...
随机推荐
- Codeforces 348D DP + LGV定理
题意及思路:https://www.cnblogs.com/chaoswr/p/9460378.html 代码: #include <bits/stdc++.h> #define LL l ...
- jQuery实现网页定位导航
代码: <!doctype html> <html> <head> <meta charset="UTF-8"> <title ...
- flexbox布局一
flexbox布局是一种新的css布局,flex是flexible的简写,所以flexbox就可以理解为可伸缩布局.而可伸缩性也是flexbox布局的亮点,至于如何可伸缩,看完下面的介绍大家应该就会有 ...
- Java高并发网络编程(四)Netty
在网络应用开发的过程中,直接使用JDK提供的NIO的API,比较繁琐,而且想要进行性能提升,还需要结合多线程技术. 由于网络编程本身的复杂性,以及JDK API开发的使用难度较高,所以在开源社区中,涌 ...
- Java异常架构与异常关键字
Java异常简介 Java异常是Java提供的一种识别及响应错误的一致性机制. Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性.在有效使用异常的情况 ...
- 初试avalon
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Vue学习笔记【3】——Vue指令之v-bind的三种用法
直接使用指令v-bind 使用简化指令: 在绑定的时候,拼接绑定内容::title="btnTitle + ', 这是追加的内容'" <!DOCTYPE html> & ...
- PHP FILTER_SANITIZE_NUMBER_INT 过滤器
定义和用法 FILTER_SANITIZE_NUMBER_INT 过滤器删除数字中所有非法的字符. 该过滤器允许所有数字以及 . + - Name: "number_int" ID ...
- 误将SELINUXTYPE看成SELINUX后,将其值改为disabled。导致操作系统服务启动,无法进入单用户模式
环境:Redhat 6.4 ORACLE11g RAC 在安装ORACLE11g之前需要关闭操作系统的防火墙和SELinux. 1.关闭防火墙:iptables -F————————————清除防火墙 ...
- ElasticSearch Roaring bitmap 和跳表联合查询
ElasticSearch Roaring map 先把所有数按65535划分, 划分方法就是求商和余数,商代表数字最终在哪一块,余数代表最终在块内的数字 比如 1, 65536, 65537, 13 ...