题目

题目大意

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

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

支持修改点权操作。


思考历程

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

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

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

然后题目就变成了一个似乎很经典的问题:给你\(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. JS window对象 Location对象 location用于获取或设置窗体的URL,并且可以用于解析URL。 语法: location.[属性|方法]

    Location对象 location用于获取或设置窗体的URL,并且可以用于解析URL. 语法: location.[属性|方法] location对象属性图示: location 对象属性: lo ...

  2. sysprep

    今天做虚拟机模板,以及克隆.单纯的克隆会造成很多冲突问题的产生,所以在这里,windows自带的sysprep功能很好的解决了这一点. 路径位于:C:\Windows\System32\Sysprep ...

  3. hbuilder 配置app为沉浸式状态栏

  4. kubeadm部署k8s集群

    kubeadm是官方社区推出的一个用于快速部署kubernetes集群的工具. 这个工具能通过两条指令完成一个kubernetes集群的部署: # 创建一个 Master 节点 kubeadm ini ...

  5. Java——类之间的关系

    3.7 类之间的关系 3.7.1 泛化关系 类和类之间的继承关系及接口与接口之间的继承关系. 3.7.2 实现关系 类对接口的实现. 3.7.3 关联关系 类与类之间的连接,一个类可以知道另一个类的属 ...

  6. Java分支结构

    Java 分支结构 - if...else/switch 顺序结构只能顺序执行,不能进行判断和选择,因此需要分支结构. Java有两种分支结构: if语句 switch语句 if语句 一个if语句包含 ...

  7. Delphi 实现简易语音发音(基于TTS方式)

    uses Comobj; procedure TForm1.Button1Click(Sender: TObject); var voice: OLEVariant; begin voice := C ...

  8. BZOJ 3230: 相似子串(后缀数组)

    传送门 解题思路 其实题目挺好想的.首先子串排名可以由后缀数组求得,因为不算重复的,所以后缀数组的每个后缀排名的去掉\(lcp\)的前缀排名为当前后缀的子串排名.这样就可以预处理出每个后缀的\(l,r ...

  9. NX二次开发-NXOpen::Drawings::DrawingSheet Class Reference

    NX11+VS2013 #include <NXOpen/Section.hxx> #include <NXOpen/SectionCollection.hxx> #inclu ...

  10. LInux多线程编程----线程特定数据的处理函数

    1.pthread_key_t和pthread_key_create() 线程中特有的线程存储, Thread Specific Data .线程存储有什么用了?他是什么意思了?大家都知道,在多线程程 ...