BZOJ3589 动态树[树剖/暴力/容斥]

操作0,显然直接线段树解决。

操作1,瓶颈在于重叠的链只算一次。在线段树上来看,如果一个区间被覆盖了,那么只算这个区间,子树里面也就不管了。

考虑对节点打标记来表示是否覆盖。但是,如果统一打完之后,并不方便计算打上标记的点的和。明确目标,现在希望能覆盖很多小区间的一个大区间被打上标记之后用他来更新答案。`````

可以对每一个点维护$acc_i$表示这个点子树内被覆盖的区间的和。那么,当有更大的区间覆盖上去的时候,直接把$acc_i$改成$sum_i$,传上去即可,同时在这个点打上已覆盖的标记。否则正常pushup。答案是$acc_{root}$

这样就保证了覆盖后答案只算一次的正确性。

还有一个问题,在操作完之后,显然应当把标记删掉。这里考虑区间覆盖性标记$tag$,当他是1的时候就是之前的完全覆盖,是-1表示没有覆盖,为了使得下一操作时清除$acc$记录,设0表示要把子树内覆盖的区间清掉。

在下放标记的时候,就可以保证左右子树以及根的$acc$都是0,能重新用。当$tag$是1的时候,可以正常下放,也可以不下放,但将0和1统一一下code更简单。复杂度$O(nklog^2n)$

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<cmath>
 6 #define dbg(x) cerr << #x << " = " << x <<endl
 7 using namespace std;
 8 typedef long long ll;
 9 typedef double db;
10 typedef pair<int,int> pii;
11 template<typename T>inline T _min(T A,T B){return A<B?A:B;}
12 template<typename T>inline T _max(T A,T B){return A>B?A:B;}
13 template<typename T>inline char MIN(T&A,T B){return A>B?(A=B,1):0;}
14 template<typename T>inline char MAX(T&A,T B){return A<B?(A=B,1):0;}
15 template<typename T>inline void _swap(T&A,T&B){A^=B^=A^=B;}
16 template<typename T>inline T read(T&x){
17     x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1;
18     while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x;
19 }
20 const int N=2e5+7;
21 struct thxorz{int to,nxt;}G[N<<1];
22 int hd[N],tot;
23 int n,q;
24 inline void Addedge(int x,int y){
25     G[++tot].to=y,G[tot].nxt=hd[x],hd[x]=tot;
26     G[++tot].to=x,G[tot].nxt=hd[y],hd[y]=tot;
27 }
28 #define y G[j].to
29 int fa[N],d[N],son[N],cnt[N],topfa[N],st[N],tim;
30 void dfs1(int x,int f){
31     fa[x]=f,d[x]=d[f]+1,cnt[x]=1;int tmp=-1;
32     for(register int j=hd[x];j;j=G[j].nxt)if(y^f)dfs1(y,x),cnt[x]+=cnt[y],MAX(tmp,cnt[y])&&(son[x]=y);
33 }
34 void dfs2(int x,int topf){
35     topfa[x]=topf,st[x]=++tim;if(!son[x])return;dfs2(son[x],topf);
36     for(register int j=hd[x];j;j=G[j].nxt)if(y^fa[x]&&y^son[x])dfs2(y,y);
37 }
38 #undef y
39 #define lc i<<1
40 #define rc i<<1|1
41 int sumv[N<<2],acc[N<<2],stag[N<<2],atag[N<<2];
42 inline void Pushup(int i){sumv[i]=sumv[lc]+sumv[rc],acc[i]=acc[lc]+acc[rc];}
43 inline void pd_sum(int i,int L,int R){
44     if(stag[i]){
45         int mid=L+R>>1;
46         sumv[lc]+=(mid-L+1)*stag[i],sumv[rc]+=(R-mid)*stag[i],stag[lc]+=stag[i],stag[rc]+=stag[i],stag[i]=0;
47     }
48 }
49 inline void pd_acc(int i){
50     if(~atag[i])
51         acc[lc]=atag[i]?sumv[lc]:0,acc[rc]=atag[i]?sumv[rc]:0,atag[rc]=atag[lc]=atag[i],atag[i]=-1;
52 }
53 void Update_add(int i,int L,int R,int ql,int qr,int k){
54     if(ql<=L&&qr>=R){sumv[i]+=(R-L+1)*k,stag[i]+=k;return;}
55     int mid=L+R>>1;pd_sum(i,L,R);
56     if(ql<=mid)Update_add(lc,L,mid,ql,qr,k);
57     if(qr>mid)Update_add(rc,mid+1,R,ql,qr,k);
58     Pushup(i);
59 }
60 void Update_account(int i,int L,int R,int ql,int qr){
61     if(ql<=L&&qr>=R){acc[i]=sumv[i],atag[i]=1;return;}
62     int mid=L+R>>1;pd_sum(i,L,R),pd_acc(i);
63     if(ql<=mid)Update_account(lc,L,mid,ql,qr);
64     if(qr>mid)Update_account(rc,mid+1,R,ql,qr);
65     Pushup(i);
66 }
67 inline void clear_the_tree(){atag[1]=0,acc[1]=0;}
68 inline void Tree_query(int x,int y){
69     if(d[x]<d[y])x^=y^=x^=y;
70     while(topfa[x]^topfa[y])Update_account(1,1,n,st[topfa[x]],st[x]),x=fa[topfa[x]];
71     Update_account(1,1,n,st[y],st[x]);
72 }
73 
74 int main(){//freopen("test.in","r",stdin);freopen("test.ans","w",stdout);
75     read(n);for(register int i=1,x,y;i<n;++i)read(x),read(y),Addedge(x,y);
76     dfs1(1,0),dfs2(1,1);
77     read(q);memset(atag,-1,sizeof atag);
78     for(register int i=1,opt,k,x,y;i<=q;++i){
79         read(opt);
80         if(opt){
81             read(k);while(k--)read(x),read(y),Tree_query(x,y);
82             printf("%d\n",acc[1]&0x7fffffff);clear_the_tree();
83         }
84         else read(x),read(k),Update_add(1,1,n,st[x],st[x]+cnt[x]-1,k);
85     }
86     return 0;
87 }
View Code

还有一个点,对$2^{31}$取模用int自然溢出是因为前31位爆掉,进位到符号位,符号位会变化,但不影响前31位的mod结果。最后只要把符号位改一下即可,所以要$\text{and}2^{31}-1$。

反思:标记的表示设计想的不太好。当线段树设计有限制条件的询问时,可以维护一些附加信息,如本题的覆盖区间的和。

附:本题还有另外两种做法。

一:暴力

每次询问把跳过的每条重链全弄出来,把这些区间按dfs序从小到大排序,然后合并区间,暴力查询。复杂度$O(n(klognlog(klogn)+klog^2n))$。比较卡。

二:容斥

这个询问相当于求若干链的并,可以容斥。


$sum(\bigcup\limits_{i=1}^{n}A_i)=\sum\limits_{k=1}^{n}(-1)^{k-1}\sum\limits_{1<=j_1<j_2<...<j_k<=n}sum(A_{j_1}\cap A_{j_2}\cap ...\cap A_{j_k})$

参考这篇

posted @ 2019-09-26 10:07  Ametsuji_akiya  阅读(130)  评论(0编辑  收藏  举报