[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)) 这样会挂是因为在中途体力值可能会更小,所以考虑求走完每棵子树所需的至少体力值 考虑从子树往上推求出当前点的答案 设每棵子树从根往 ...
随机推荐
- START TRANSACTION - 开始一个事务块
SYNOPSIS START TRANSACTION [ ISOLATION LEVEL { READ COMMITTED | SERIALIZABLE } ] [ READ WRITE | READ ...
- vue css动画原理
从隐藏到显现 从显现到隐藏 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> ...
- spring.xml及注解
spring.xml配置文件中配置注解: 开启注解(及自动扫描包中bean): 1:<context:component-scan base-package="com.bzu" ...
- 创建TCP服务器和TCP客户端
import sockethost='127.0.0.1'port=8080web=socket.socket()web.bind((host,port))web.listen(5)# 设置最多连接数 ...
- 对AngularJs的简单了解
一.简单介绍 AngularJS是为了克服HTML在构建应用上的不足而设计的.HTML是一门很好的为静态文本展示设计的声明式语言,但要构建WEB应用的话它就显得乏力了.所以我做了一些工作(你也可以觉得 ...
- asp.net MVC遇到的问题
参考:http://blog.csdn.net/chenqiangdage/article/details/48996101 asp.netMVC 如何解决“上下文模型已在数据库创建后发生更改” 问题 ...
- MySql查询结果按照指定顺序排序
Mysql这功能做的很好用啊!! 让查询结果按照指定顺序排序 --------------------------------------------------------------------- ...
- 生产环境用到的几个有用的Linux命令
有时候,几个有用的Linux命令可以很大的提高你的工作效率. 1.free -m 这个命令我暂时就只会这么使用,它可以查看服务器的内存资源 2.top 这个命令同样可以查看服务器的资源,当然我还是用它 ...
- DELPHI中枚举类型数据的介绍和使用方法
在看delphi程序的时候看到aa=(a,b,c,d);这样的东西,还以为是数组,同事说是函数,呵呵,当然这两个都不屑一击,原来这样式子是在声明并付值一个枚举类型的数据.下边写下来DELPHI中枚举类 ...
- Linux环境进程间通信----信号与管道
一.信号: 信号主要用来通知进程异步事件的发生.可以使用“kill -l ”命令来显示系统中的信号.进程可以忽略大部分信号,但是有两个是不能忽略的: (1)SIGSTOP:这个信号将中断进程的执行. ...