题目大意
给你一棵树,带点权和边权。
要你选择一个联通子图,使得点权和乘最小边权最大。 支持修改点权操作。思考历程
显然,最先想到的当然是重构树了……
重构树就是在做最大生成树的时候,当两个联通块相连时,新增一个点,将两个联通块的根节点连上去。 这个新建的点上记录这条边的边权,那么以它为子树的答案就是子树的点权和乘上自己表示的这条边的边权。然后题目就变成了一个似乎很经典的问题:给你\(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#include #include #include #define N 300010#define ll long longinline int input(){ char ch=getchar(); while (ch<'0' || '9' 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] 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
总结
分块大法好啊……