题目

题目大意

给你一棵树,带点权和边权。

要你选择一个联通子图,使得点权和乘最小边权最大。

支持修改点权操作。


思考历程

显然,最先想到的当然是重构树了……

重构树就是在做最大生成树的时候,当两个联通块相连时,新增一个点,将两个联通块的根节点连上去。

这个新建的点上记录这条边的边权,那么以它为子树的答案就是子树的点权和乘上自己表示的这条边的边权。

然后题目就变成了一个似乎很经典的问题:给你\(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】小ω的树的更多相关文章

  1. 6359. 【NOIP2019模拟2019.9.15】小ω的树(tree)(定期重构)

    题目描述 题解 qy的毒瘤题 CSP搞这种码农题当场手撕出题人 先按照边权从大到小建重构树,然后40%暴力修改+查找即可 100%可以定期重构+平衡规划,每次把B个询问拉出来建虚树,在虚树上暴力维护每 ...

  2. 6358. 【NOIP2019模拟2019.9.15】小ω的仙人掌

    题目 题目大意 给你一串二元组\((a_i,b_i)\)的数列. 求最小的区间\([l,r]\)长度,满足\([l,r]\)中的每个二元组选或不选,使得\(\sum a_i=w\)且\(\sum b_ ...

  3. 6368. 【NOIP2019模拟2019.9.25】质树

    题目 题目大意 有个二叉树,满足每个点跟它的所有祖先互质. 给出二叉树的中序遍历的点权,还原一种可能的方案. 思考历程 首先想到的当然是找到一个跟全部互质的点作为根,然后左右两边递归下去处理-- 然而 ...

  4. 6389. 【NOIP2019模拟2019.10.26】小w学图论

    题目描述 题解 之前做过一次 假设图建好了,设g[i]表示i->j(i<j)的个数 那么ans=∏(n-g[i]),因为连出去的必定会构成一个完全图,颜色互不相同 从n~1染色,点i的方案 ...

  5. 6380. 【NOIP2019模拟2019.10.06】小w与最长路(path)

    题目 题目大意 给你一棵树,对于每一条边,求删去这条边之后,再用一条边(自己定)连接两个连通块,形成的树的直径最小是多少. 正解 首先,将这棵树的直径给找出来.显然,如果删去的边不在直径上,那么答案就 ...

  6. 6424. 【NOIP2019模拟2019.11.13】我的订书机之恋

    题目描述 Description Input Output Sample Input 见下载 Sample Output 见下载 Data Constraint 题解 lj题卡线段树 求出每个右端点往 ...

  7. 6407. 【NOIP2019模拟11.05】小 D 与随机

    题目描述 Description Input 第一行两个个整数 n,k. 之后 n -1 行,第 i 行两个整数 ui, vi, 表示一条树边. 保证输入的数据构成一棵树. Output 一行一个数表 ...

  8. 6392. 【NOIP2019模拟2019.10.26】僵尸

    题目描述 题解 吼题但题解怎么这么迷 考虑一种和题解不同的做法(理解) 先把僵尸离散化,h相同的钦(ying)点一个大小 (可以发现这样每种情况只会被算正好一次) 计算完全被占领的方案,然后1-方案/ ...

  9. 6364. 【NOIP2019模拟2019.9.20】养马

    题目描述 题解 一种显然的水法:max(0,-(点权-边权之和*2)) 这样会挂是因为在中途体力值可能会更小,所以考虑求走完每棵子树所需的至少体力值 考虑从子树往上推求出当前点的答案 设每棵子树从根往 ...

随机推荐

  1. Codeforces 1175F 尺取法 性质分析

    题意:给你一个数组,问有多少个区间,满足区间中的数构成一个排列. 思路(大佬代码):我们发现,一个排列一定含有1,所以我们不妨从1开始入手计算构成排列的区间个数.对于每个扫描到的1(假设处于位置i), ...

  2. java笔试题大全带答案(经典11题)

    1.不通过构造函数也能创建对象吗()A. 是B. 否分析:答案:AJava创建对象的几种方式(重要):(1) 用new语句创建对象,这是最常见的创建对象的方法.(2) 运用反射手段,调用java.la ...

  3. javac无效问题解决

    首先去下载JDK的最新版本,目前应该是1.7,具体下载地址可以百度去搜索下载 步骤阅读 2 下载时候要注意自己系统的版本,JDK分32位和64位版,根据自己系统版本下载. 步骤阅读 3 下载完后安装, ...

  4. vue v-modle修饰符.number .trim

    语法糖: 在不影响功能的情况下,添加某种方法实现同样的效果,从而方便程序开发. .number:可以将输入转换成Number类型,否则虽然输入的是数字,但它的类型其实是String. .trim:自动 ...

  5. lombok 注解简单介绍

    一.Lombok 的简单介绍和使用 Lombok是一个可以帮助我们简化 Java 代码编写的工具类,通过采用注解的方式简化了 JavaBean 的编写,使我们写的类更加简洁. 1. 添加 Lombok ...

  6. Ubuntu系统管理systemd

    systemctl命令 systemctl list-units - 列出所有jobs/serviceList all units (where unit is the term for a job/ ...

  7. leetcood学习笔记-35-二分法

    题目: 第一次提交; class Solution: def searchInsert(self, nums: List[int], target: int) -> int: for i in ...

  8. 安全检测及分析神器—AppScan使用教程

    最近项目准备验收,所以最近在做项目验收的准备工作:我们公司规定,项目的安全检测必须通过才能进行项目验收:公司的安全部门用的检测软件就是大名鼎鼎的IBM Rational Appscan;在教由安全部门 ...

  9. AcWing 139. 回文子串的最大长度 hash打卡

    如果一个字符串正着读和倒着读是一样的,则称它是回文的. 给定一个长度为N的字符串S,求他的最长回文子串的长度是多少. 输入格式 输入将包含最多30个测试用例,每个测试用例占一行,以最多1000000个 ...

  10. 1、Locust压力测试环境搭建

    环境准备:阿里云服务器一台.python2.7.pip Locust 介绍 Locust 是一个开源负载测试工具.使用 Python 代码定义用户行为,也可以仿真百万个用户. Locust 简单易用, ...