参考链接(历史最长 雾):
例题:poj1741 bzoj1095 bzoj3730 bzoj3924 bzoj3435
前置技能:点分治
例0 poj1741
一棵不会动的树,询问树上距离<=k的点对数量。
友情提示:下文中“这坨树”指的都是树上的一个联通块,一般指的是重心管辖的这一联通块。
我们考虑进行点分治,对于每坨树,我们找到重心,统计过重心的这样的点对个数,然后对于把这坨树从重心劈开,对于每一部分分别分治。实现细节这里略过(雾),大概就是从每个重心开始dfs,把深度排序完two-pointer一下,但是这样对于在同一棵子树中的会误统计,那么就把同一棵子树的再扣掉一遍即可。
例1 bzoj1095
还是一棵不会动的树,每个点有一个颜色(黑色或白色),一开始全是黑色,有一些修改和询问,修改是将某个点的颜色反转,询问黑点间的最长距离。
假设我们现在没有修改,只有询问,点分治的做法就和上面一题类似,每次分治时找到每棵子树深度最大的一个黑点,用最大的两个统计一下即可。
我们考虑点分治的时候把每一层的重心连向下一层重心,这样形成了一棵以整棵树重心为根的新树。
我们考虑暴力一点,在每个点开两个堆,第一个堆插入这个重心管辖的一坨树所有黑点到分治树上这个点父亲的距离,第二个堆插入所有点分治树上孩子的堆顶,这样我们就可以对于每个分治重心,找到分属两棵子树的深度最大的两个黑点,即这个点堆的最大和次大值。
注意如果分治树上的重心是白点,那么不应该用单独一个来更新答案,如果只用一个黑点来更新答案是不合法的。
为了统计答案,我们显然还要再开一个堆来维护答案。
修改就只要在点分治树上,从自己那个重心往根走,维护一下那些堆即可。还是要注意上面这个更新答案不能用单独一个。
显然复杂度是log^2的,要在堆中删除可以用经典trick(用另一个堆打删除标记)。
#includeusing namespace std;#define pb push_backtypedef pair pii;typedef vector vi;#define fi first#define se second#define Edg int M=0,fst[SZ],vb[SZ],nxt[SZ];void ad_de(int a,int b){++M;nxt[M]=fst[a];fst[a]=M;vb[M]=b;}void adde(int a,int b){ad_de(a,b);ad_de(b,a);}#define esb(x,e,b) (int e=fst[x],b=vb[e];e;e=nxt[e],b=vb[e])#define SZ 666666struct Heap{priority_queue h,d;int sz;inline void cp_(){ while(d.size()&&h.top()==d.top()) h.pop(), d.pop();}inline void push(int x) {h.push(x); ++sz;}inline void del(int x) {d.push(x); --sz;}inline int size() { return sz;}inline int top(){ if(!sz) return 0; cp_(); return h.top();}inline int s2(bool c=0){ if(!sz) return 0; if(sz==1) return c?0:top(); int x=top(); h.pop(); int r=top(); h.push(x); return x+r;}inline void pop() {cp_(); h.pop(); --sz;}inline void op(int x,int o){ if(!o) push(x); else del(x);}}a,b[SZ],c[SZ];Edgint dep[SZ],fs[SZ],ed[SZ],S=0;#define P 20 int minn[P][SZ],*ds=minn[0];void dfs(int x,int f=0){ dep[x]=dep[f]+1; ds[fs[x]=ed[x]=++S]=dep[x]; for esb(x,e,b)if(b!=f) { dfs(b,x); ds[ed[x]=++S]=dep[x]; }}int l2[SZ];void pre(){ l2[0]=-1; for(int i=1;i<=S;i++) l2[i]=l2[i>>1]+1; for(int i=1;i fs[b]) swap(a,b); int l=fs[a],r=ed[b],x=l2[r-l+1]; int mi=min(minn[x][l],minn[x][r-(1< ='0'&&c<='9')int aa,bb;int F(){ while(ch=getc(),!isd(ch)&&ch!='-');ch=='-'?aa=bb=0:(aa=ch-'0',bb=1); while(ch=getc(),isd(ch))aa=aa*10+ch-'0';return bb?aa:-aa;}#define gi F()int main(){ n=gi; int tot=n; for(int i=1;i
此外还有一种基于括号序列的做法,先mark,改天学
例2 bzoj3924
还是一棵不会动的树,边有长度,每个点i有一个点权di,开始都为0,有q次对某个点点权的修改,每次修改之后你需要选出一个点s,最小化$\sum_vd_vdis(s,v)$并输出。
如果所有d都是1,我们可以发现这就是要求重心,那么有了d就是要求带修改带权重心。
首先我们假装没有修改,询问某些固定的s,要如何计算这个给定值?
那对于一个s和每个v,对于每个重心w,如果s到v的路径经过w,那么dis(s,v)=dis(s,w)+dis(w,v),dis(s,w)是定值,我们预先统计d[v]之和乘上去即可,所有dis(w,v)d[v]也可以预先统计。
那么考虑在点分治树从s往上往上爬,假设从重心a爬到了重心b,那么b这一坨树去掉a这一坨树,里面的点到s的路径都经过b,然后直接统计即可。
这样搞显然是可以支持修改的,只要同样往上爬并更新信息即可。
那么现在问题就是要确定这个s,怎么确定...我也不是很清楚啊。网上的题解都是随便找个点,然后如果有一棵子树答案比它小就往那边走。这不是随便卡吗(复杂度不对)...所以代码就不写了(大雾)等我会了再来填坑
UPD:好像可以把复杂度改对,如果一个点有多个孩子,可以把孩子建成线段树那样,强制把每个点变成不超过两个孩子,这样走复杂度听说就对了。
例3 bzoj3730
又是一棵不会动的树,每个点有点权,有m次操作或询问,强制在线。
操作是改变某个点的点权,询问是询问和某个点距离不超过某个值的点权和。
这题和例2没啥区别吧...仍然考虑在点分治树从s往上往上爬,假设从重心a爬到了重心b,那么b这一坨树去掉a这一坨树,里面的点到s的路径仍然都经过b,考虑dis(s,v)=dis(s,w)+dis(w,v)<=k,对于我们dis(s,w)是定值,那么我们只要维护dis(w,v)<=某个值的点权和就好了。
因为dis(w,v)对于每个点是定值,只要开一堆vector二分+树状数组即可。为了要“去掉a这一坨树”,每个重心不仅要统计孩子信息,还要统计到父亲的信息。
下面这份代码为了节约空间,sons和sons2数组开始记的是管辖点,最后记距离。因为stl,跑的十分慢...
#includeusing namespace std;#define pb push_backtypedef pair pii;typedef vector vi;#define fi first#define se second#define Edg int M=0,fst[SZ],vb[SZ],nxt[SZ];void ad_de(int a,int b){++M;nxt[M]=fst[a];fst[a]=M;vb[M]=b;}void adde(int a,int b){ad_de(a,b);ad_de(b,a);}#define esb(x,e,b) (int e=fst[x],b=vb[e];e;e=nxt[e],b=vb[e])#define SZ 666666Edgint dep[SZ],fs[SZ],ed[SZ],S=0;#define P 20 int minn[P][SZ],*ds=minn[0];void dfs(int x,int f=0){ dep[x]=dep[f]+1; ds[fs[x]=ed[x]=++S]=dep[x]; for esb(x,e,b)if(b!=f) { dfs(b,x); ds[ed[x]=++S]=dep[x]; }}int l2[SZ];void pre(){ l2[0]=-1; for(int i=1;i<=S;i++) l2[i]=l2[i>>1]+1; for(int i=1;i fs[b]) swap(a,b); int l=fs[a],r=ed[b],x=l2[r-l+1]; int mi=min(minn[x][l],minn[x][r-(1< ='0'&&c<='9')int aa,bb;int F(){ while(ch=getc(),!isd(ch)&&ch!='-');ch=='-'?aa=bb=0:(aa=ch-'0',bb=1); while(ch=getc(),isd(ch))aa=aa*10+ch-'0';return bb?aa:-aa;}#define gi F()int main(){ n=gi,m=gi; for(int i=1;i<=n;i++) val[i]=gi; for(int i=1;i
例4 紫荆花之恋
咳咳,这题就先暂时坑在这里...为什么大家应该都懂。