ctsc2018的D2T1(主席树模板题),大家都半个小时AC了,我因为一个sb bug调了2个多小时……
博主是个大sb。
一个长度为n的序列a,设其排过序之后为b,其中位数定义为b[n/2],其中a,b从0开始标号,除法取下整。
给你一个长度为n的序列s。回答Q个这样的询问:s的左端点在[a,b]之间,右端点在[c,d]之间的子序列中,最大的中位数。
其中a<b<c<d。位置也从0开始标号。强制在线。
如果询问只有一个,我们当然可以二分答案,把$geq mid$的置为1,其他置为-1,
然后就是求满足左端点在$[a,b]$之间,右端点在$[c,d]$之间的最大权值的子序列的权值是否$geq 0$
那么对于$[b,c]$之间的所有数,是肯定要选的,那么$[a,b-1]$的最大后缀、$[b,c]$、$[c+1,d]$的最大前缀拼起来就是答案
可以用线段树
对于多组询问,我们肯定不能每次二分一个答案就把所有点权都重置一遍
所以就用主席树,第$i$棵树是二分答案的$mid=i$时查找的线段树,就是$<i$的点权都是-1
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> #include<vector> using namespace std; #define ll long long #define db double #define For(i,a,b) for(int i=(a);i<=(b);++i) #define Rep(i,a,b) for(int i=(a);i>=(b);--i) #define lc son[pos][0] #define rc son[pos][1] const int maxn=2e4+7,maxm=1e7+7; int n,m,a[maxn],p[maxn],TOT,tot,ans; vector<int> G[maxn]; char cc;ll ff; template<typename T>void read(T& aa) { aa=0;ff=1; cc=getchar(); while(cc!='-'&&(cc<'0'||cc>'9')) cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } struct Node{ int ld,rd,sum; Node(){} Node(int ld,int rd,int sum):ld(ld),rd(rd),sum(sum){} Node operator + (const Node& b) { Node o; o.ld=max(ld,sum+b.ld); o.rd=max(b.rd,rd+b.sum); o.sum=sum+b.sum; return o; } }node[maxm]; int son[maxm][2]; int ql,qr,qx; void get_bld(int pos,int l,int r) { if(l==r) { node[pos]=Node(1,1,1); return; } int mid=(l+r)>>1; get_bld(lc=++tot,l,mid); get_bld(rc=++tot,mid+1,r); node[pos]=node[lc]+node[rc]; } void bld(int& pos,int last,int l,int r) { if(!pos) { pos=++tot; lc=son[last][0]; rc=son[last][1]; } if(l==r) { node[pos]=Node(0,0,-1); return; } int mid=(l+r)>>1; if(qx<=mid) { if(lc==son[last][0]) lc=0; bld(lc,son[last][0],l,mid); } else { if(rc==son[last][1]) rc=0; bld(rc,son[last][1],mid+1,r); } node[pos]=node[lc]+node[rc]; } Node q(int pos,int l,int r) { if(l>=ql&&r<=qr) return node[pos]; int mid=(l+r)>>1; if(qr<=mid) return q(lc,l,mid); if(ql>mid) return q(rc,mid+1,r); return q(lc,l,mid)+q(rc,mid+1,r); } bool check(int x,int l1,int r1,int l2,int r2) { Node L,O,R; L=R=O=Node(0,0,0); ql=l1; qr=r1-1; L=q(x,1,n); ql=l2+1; qr=r2; R=q(x,1,n); ql=r1; qr=l2; O=q(x,1,n); return L.rd+O.sum+R.ld>=0; } int get_ans(int x,int y,int z,int w) { if(x>y) swap(x,y); if(y>z) swap(y,z); if(z>w) swap(z,w); if(x>y) swap(x,y); if(y>z) swap(y,z); if(x>y) swap(x,y); int l1=x,r1=y,l2=z,r2=w; // printf("get_ans:%d~%d,%d~%d ",l1,r1,l2,r2); int l=1,r=TOT,mid; if(check(r,l1,r1,l2,r2)) return r; while(l<r-1) { mid=(l+r)>>1; if(check(mid,l1,r1,l2,r2)) l=mid; else r=mid; } return l; } int main() { read(n); int x,y,z,w; For(i,1,n) read(a[i]),p[i]=a[i]; sort(p+1,p+n+1); TOT=unique(p+1,p+n+1)-(p+1); For(i,1,n) a[i]=lower_bound(p+1,p+TOT+1,a[i])-p; For(i,1,n) G[a[i]].push_back(i); tot=TOT; get_bld(1,1,n); For(i,2,TOT) { son[i][0]=son[i-1][0]; son[i][1]=son[i-1][1]; node[i]=node[i-1]; x=G[i-1].size(); For(j,0,x-1) { qx=G[i-1][j]; bld(i,i-1,1,n); } } read(m); For(i,1,m) { read(x); read(y); read(z); read(w); x=(x+ans)%n+1; y=(y+ans)%n+1; z=(z+ans)%n+1; w=(w+ans)%n+1; ans=get_ans(x,y,z,w); printf("%d ",ans=p[ans]); } return 0; }
给一个长度为$n$的序列$a$。$1 leq a[i] leq n$。
$m$组询问,每次询问一个区间$[l,r]$,是否存在一个数在$[l,r]$中出现的次数大于$(r-l+1)/2$。如果存在,输出这个数,否则输出0。
一个数,如果满足条件,那么他一定是中位数,所以直接找区间的中位数,然后再查询它出现次数
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define ll long long #define db double #define For(i,a,b) for(int i=(a);i<=(b);++i) #define Rep(i,a,b) for(int i=(a);i>=(b);--i) #define lc son[pos][0] #define rc son[pos][1] const int maxn=5e5+7,maxm=1e7+7; int n,m,tot; char cc;ll ff; template<typename T>void read(T& aa) { aa=0;ff=1; cc=getchar(); while(cc!='-'&&(cc<'0'||cc>'9')) cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } int sum[maxm],son[maxm][2],qx,qy; void bld(int pos,int last,int l,int r) { sum[pos]=sum[last]+1; if(l==r) return; int mid=(l+r)>>1; if(qx<=mid) rc=son[last][1],bld(lc=++tot,son[last][0],l,mid); else lc=son[last][0],bld(rc=++tot,son[last][1],mid+1,r); } int q(int ld,int rd,int l,int r) { if(l==r) { if(sum[rd]-sum[ld]>=qy) return l; return 0; } int mid=(l+r)>>1,x=sum[son[rd][0]]-sum[son[ld][0]]; if(x>=qx) return q(son[ld][0],son[rd][0],l,mid); qx-=x; return q(son[ld][1],son[rd][1],mid+1,r); } int main() { read(n); read(m); tot=n; For(i,1,n) { read(qx); bld(i,i-1,1,n); } int x,y; For(i,1,m) { read(x); read(y); qx=qy=(y-x+1)/2+1; printf("%d ",q(x-1,y,1,n)); } return 0; }
有一个长度为n的数组{a1,a2,...,an}。m次询问,每次询问一个区间内最小没有出现过的自然数。
$n,m leq 2*10^5$
主席树
第$i$棵线段树(权值线段树),表示的是我们如果只考虑数组的前i个数,那么每个数$x$出现的最大位置$f(x)$是在哪,维护区间最小值
对于询问$(l,r)$,我们在第$r$棵线段树上二分,找到最大的$p$,使得$min(f(1),f(2),...,f(p-1)) geq l$
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define ll long long #define db double #define For(i,a,b) for(int i=(a);i<=(b);++i) #define Rep(i,a,b) for(int i=(a);i>=(b);--i) #define lc son[pos][0] #define rc son[pos][1] const int maxn=2e5+7,maxm=1e7+7; int n,m,W,tot; char cc;ll ff; template<typename T>void read(T& aa) { aa=0;ff=1; cc=getchar(); while(cc!='-'&&(cc<'0'||cc>'9')) cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } int num[maxm],son[maxm][2],ql,qr,qx; void bld(int pos,int last,int l,int r) { if(l==r) {num[pos]=qx;return;} int mid=(l+r)>>1; if(ql<=mid) rc=son[last][1],bld(lc=++tot,son[last][0],l,mid); else lc=son[last][0],bld(rc=++tot,son[last][1],mid+1,r); num[pos]=min(num[lc],num[rc]); } int q(int pos,int l,int r) { if(l==r) return l; int mid=(l+r)>>1; if(num[lc]<qx) return q(lc,l,mid); return q(rc,mid+1,r); } int main() { read(n); read(m); W=n+1; tot=n; int x,y; For(i,1,n) { read(x); ++x; if(x>n) { son[i][0]=son[i-1][0]; son[i][1]=son[i-1][1]; continue; } ql=qr=x; qx=i; bld(i,i-1,1,W); } For(i,1,m) { read(x); read(y); qx=x; printf("%d ",q(y,1,W)-1); } return 0; }
小Z有一片森林,含有$N$个节点,每个节点上都有一个非负整数作为权值。初始的时候,森林中有$M$条边。
小Z希望执行$T$个操作,操作有两类:
Q x y k查询点$x$到点$y$路径上所有的权值中,第$k$小的权值是多少。此操作保证点$x$和点$y$连通,同时这两个节点的路径上至少有$k$个点。
L x y在点$x$和点$y$之间连接一条边。保证完成此操作后,仍然是一片森林。
为了体现程序的在线性,我们把输入数据进行了加密。设$lastans$为程序上一次输出的结果,初始的时候$lastans$为0。
对于一个输入的操作Q x y k,其真实操作为Q x^lastans y^lastans k^lastans。
对于一个输入的操作L x y,其真实操作为L x^lastans y^lastans。其中^运算符表示异或,等价于pascal中的xor运算符。
请写一个程序來帮助小Z完成这些操作。
$N,M,T leq 8*10^4$
主席树+启发式合并,每颗线段树维护到根的路径的信息,每次合并时,直接dfs,顺便插入线段树。
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define ll long long #define db double #define For(i,a,b) for(int i=(a);i<=(b);++i) #define Rep(i,a,b) for(int i=(a);i>=(b);--i) #define lc son[pos][0] #define rc son[pos][1] const int maxn=8e4+7,maxm=1e7+7,maxt=23,W=19; int Td,n,m,Q,w[maxn],p[maxn],TOT,tot,ans; char s[17]; char cc;ll ff; template<typename T>void read(T& aa) { aa=0;ff=1; cc=getchar(); while(cc!='-'&&(cc<'0'||cc>'9')) cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } int f[maxn],size[maxn]; int find(int x) {return x==f[x]? x:f[x]=find(f[x]);} int fir[maxn],nxt[2*maxn],to[2*maxn],e=0; void add(int x,int y) { to[++e]=y;nxt[e]=fir[x];fir[x]=e; to[++e]=x;nxt[e]=fir[y];fir[y]=e; } int root[maxn],sum[maxm],son[maxm][2],ql,qr,qx; void bld(int&pos,int last,int l,int r) { pos=++tot; sum[pos]=sum[last]+1; if(l==r) return; int mid=(l+r)>>1; if(qx<=mid) rc=son[last][1],bld(lc,son[last][0],l,mid); else lc=son[last][0],bld(rc,son[last][1],mid+1,r); } int q(int p1,int p2,int p3,int p4,int l,int r) { if(l==r) return l; int mid=(l+r)>>1,x; x=sum[son[p1][0]]+sum[son[p2][0]]-sum[son[p3][0]]-sum[son[p4][0]]; if(qx<=x) return q(son[p1][0],son[p2][0],son[p3][0],son[p4][0],l,mid); qx-=x; return q(son[p1][1],son[p2][1],son[p3][1],son[p4][1],mid+1,r); } int fa[maxn][maxt],dep[maxn]; void dfs(int pos,int f) { fa[pos][0]=f; dep[pos]=dep[f]+1; For(i,1,W) fa[pos][i]=fa[fa[pos][i-1]][i-1]; qx=w[pos]; bld(root[pos],root[f],1,TOT); int y,z; for(y=fir[pos];y;y=nxt[y]) { if((z=to[y])==f) continue; dfs(z,pos); } } int get_lca(int x,int y) { if(dep[x]!=dep[y]) { if(dep[x]<dep[y]) swap(x,y); Rep(i,W,0) if(dep[fa[x][i]]>=dep[y]) x=fa[x][i]; } if(x==y) return x; Rep(i,W,0) if(fa[x][i]!=fa[y][i]){ x=fa[x][i]; y=fa[y][i]; } return fa[x][0]; } int Yth(int x,int y,int k) { int lca=get_lca(x,y),rs; qx=k; rs=q(root[x],root[y],root[lca],root[fa[lca][0]],1,TOT); return p[rs]; } void lk(int x,int y) { add(x,y); int a=find(x),b=find(y); if(size[a]<size[b]) swap(a,b),swap(x,y); size[a]+=size[b]; f[b]=a; dfs(y,x); } int main() { read(Td); int x,y,k,a,b; read(n); read(m); read(Q); For(i,1,n) read(w[i]),p[i]=w[i]; sort(p+1,p+n+1); TOT=unique(p+1,p+n+1)-(p+1); For(i,1,n) w[i]=lower_bound(p+1,p+TOT+1,w[i])-p; For(i,1,n) f[i]=i,size[i]=1; For(i,1,m) { read(x); read(y); add(x,y); a=find(x); b=find(y); size[a]+=size[b]; f[b]=a; } For(i,1,n) if(find(i)==i) dfs(i,0); For(i,1,Q) { scanf("%s",s+1); read(x); read(y); x^=ans; y^=ans; if(s[1]=='Q') { read(k); k^=ans; printf("%d ",ans=Yth(x,y,k)); } else lk(x,y); } return 0; }
给出$n$个三元组 $e[i]=(s_i , t_i , w_i)$
第i个三元组的价值为 $sum w_j$ ,$j$ 满足以下4个条件:
1、$j<i$
2、$t_j<t_i$
3、$s_j<s_i$
4、不存在$j<k<i$,且$sj<sk<si$
xxy大佬的题解(http://www.cnblogs.com/TheRoadToTheGold/p/8718239.html):
把每个三元组看作二维平面上的一个点$(i,s_i)$
先不考虑$t$,
那么$j$若满足要求,必须满足以$(j,s_j)$为左下角,以$(i,s_i)$为右上角的矩形内没有其他的三元组
可以用CDQ分治解决
设三元组$e[i]$的坐标为$(x,y)=(i,s_i)$
先将所有的三元组按$y$排序,然后按$x$归并
即左右两边归并时,左边所有三元组的$y$小于右边所有三元组的$y$
归并结束后,左右两边合并为$x$递增的集合
考虑左边对右边的贡献
在归并的过程中维护两个单调栈$L$和$R$
栈$L$ 维护左边的三元组,满足$x$单调递增,$y$单调递减
栈$R$ 维护右边的三元组,满足$x$单调递增,$y$单调递增,且栈顶的$y$一定小于当前的$y$
对于右边的一个三元组$j$,左边对其有贡献的三元组$i$满足
1、$i<j$,因为是按$x$归并,所以此条件一定满足
2、$i$在栈$L$中,如果$i$不在栈$L$中,说明$i$后面,$j$前面存在一个$k$,满足$s_i<s_k<s_j$
3、设栈$r$的栈顶为$k$,$i>k$,否则这个$k$会使 $i<k<j$ 且$s_i<s_k<s_j$
我们只维护栈$L$中三元组的信息,即可满足条件2
至于条件3,因为栈$L$的$x$单调递增,二分查找第一个满足条件的,那么它到栈$L$的栈顶都满足条件
记录栈$L$中$w$的前缀和即可解决
现在再考虑$t$,只需要将前缀和改为可持久化权值线段树即可
有N个节点,标号从1到N,这N个节点一开始相互不连通。第i个节点的初始权值为a[i],接下来有如下一些操作:
U x y: 加一条边,连接第x个节点和第y个节点
A1 x v: 将第x个节点的权值增加v
A2 x v: 将第x个节点所在的连通块的所有节点的权值都增加v
A3 v: 将所有节点的权值都增加v
F1 x: 输出第x个节点当前的权值
F2 x: 输出第x个节点所在的连通块中,权值最大的节点的权值
F3: 输出所有节点中,权值最大的节点的权值
$N ,Q leq 3*10^5$
做法1:线段树。把会在同一连通块的在线段树上排在一起
做法2:左偏树。两种:第一种是每个连通块,第二种是所有第一种堆的堆顶
线段树:
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define ll long long #define db double #define For(i,a,b) for(int i=(a);i<=(b);++i) #define Rep(i,a,b) for(int i=(a);i>=(b);--i) const int maxn=3e5+7,INF=0x3f3f3f3f; int n,m,a[maxn],p[maxn],id[maxn]; char s[17]; char cc;ll ff; template<typename T>void read(T& aa) { aa=0;ff=1; cc=getchar(); while(cc!='-'&&(cc<'0'||cc>'9')) cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } int f[maxn],end[maxn],nxt[maxn]; int find(int x) {return x==f[x]? x:f[x]=find(f[x]);} void lk(int x,int y) { x=find(x); y=find(y); if(x==y) return; f[y]=x; nxt[end[x]]=y; end[x]=end[y]; } struct Act{ int op,x,y; Act(){} Act(int op,int x,int y):op(op),x(x),y(y){} }act[maxn]; int num[4*maxn],laz[4*maxn],ql,qr,qx; void ud(int pos) {num[pos]=max(num[pos<<1],num[pos<<1|1]);} void pd(int pos) { if(!laz[pos]) return; num[pos<<1]+=laz[pos]; num[pos<<1|1]+=laz[pos]; laz[pos<<1]+=laz[pos]; laz[pos<<1|1]+=laz[pos]; laz[pos]=0; } void bld(int pos,int l,int r) { if(l==r) { num[pos]=a[p[l]]; return; } int mid=(l+r)>>1; bld(pos<<1,l,mid); bld(pos<<1|1,mid+1,r); ud(pos); } void chge(int pos,int l,int r) { if(l>=ql&&r<=qr) { num[pos]+=qx; laz[pos]+=qx; return; } int mid=(l+r)>>1; pd(pos); if(ql<=mid) chge(pos<<1,l,mid); if(qr>mid) chge(pos<<1|1,mid+1,r); ud(pos); } int q(int pos,int l,int r) { if(l>=ql&&r<=qr) return num[pos]; int mid=(l+r)>>1,rs=-INF; pd(pos); if(ql<=mid) rs=max(rs,q(pos<<1,l,mid)); if(qr>mid) rs=max(rs,q(pos<<1|1,mid+1,r)); return rs; } int main() { read(n); For(i,1,n) read(a[i]),f[i]=end[i]=i; read(m); int op,x,y; For(i,1,m) { scanf("%s",s+1); x=y=0; if(s[1]=='U') op=0; else if(s[1]=='A') op=s[2]-'0'; else op=s[2]-'0'+3; if(op==0||op%3!=0) read(x); if(op<=3) read(y); act[i]=Act(op,x,y); if(op==0) lk(x,y); } y=0; For(i,1,n) if(i==find(i)) for(x=i;x;x=nxt[x]) p[id[x]=++y]=x; bld(1,1,n); For(i,1,n) f[i]=end[i]=i,nxt[i]=0; For(i,1,m) { op=act[i].op; x=act[i].x; y=act[i].y; if(op==0) lk(x,y); else { qx=y; if(op%3==1) ql=qr=id[x]; else if(op%3==2) x=find(x),ql=id[x],qr=id[end[x]]; else ql=1,qr=n; if(op<=3) chge(1,1,n); else printf("%d ",q(1,1,n)); } } return 0; }
在一条直线上有 $N$ 个炸弹,每个炸弹的坐标是 $X_i$,爆炸半径是 $R_i$,当一个炸弹爆炸时,如果另一个炸弹所在位置 $X_j$ 满足:
$X_i-R_i leq X_j leq X_i+R_i$,那么,该炸弹也会被引爆。
现在,请你帮忙计算一下,先把第$i$个炸弹引爆,将引爆多少个炸弹呢?
输入保证$X_i$严格递增。
$N leq 5*10^5 , |X_i| leq 10^{18} , R_i leq 2*10^{18}$
对于一个炸弹引爆之后,首先引爆的炸弹,是一个区间,最后所有被引爆的炸弹也是一个区间
所以我们就利用线段树优化建图,然后tarjan,然后就可以处理出每个点可以到达多少个点了
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define ll long long #define db double #define For(i,a,b) for(int i=(a);i<=(b);++i) #define Rep(i,a,b) for(int i=(a);i>=(b);--i) const ll mod=1e9+7; const int maxn=2e6+7,maxm=2e7+7; ll n,r[maxn],p[maxn],id[maxn],d[maxn],tot,ans; char cc;ll ff; template<typename T>void read(T& aa) { aa=0;ff=1; cc=getchar(); while(cc!='-'&&(cc<'0'||cc>'9')) cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } int fir[maxn],nxt[maxm],to[maxm],e=0; void add(int x,int y) { // printf("add:%d->%d ",x,y); to[++e]=y;nxt[e]=fir[x];fir[x]=e; } int FIR[maxn],NXT[maxm],TO[maxm],E=0,ind[maxn]; void ADD(int x,int y) { // printf("ADD:%d->%d ",x,y); TO[++E]=y;NXT[E]=FIR[x];FIR[x]=E; ++ind[y]; } int ld[maxn],rd[maxn],ql,qr,qx; void bld(int pos,int l,int r) { d[pos]=++tot; ld[tot]=l; rd[tot]=r; if(l==r) {id[l]=tot;return;} int mid=(l+r)>>1; bld(pos<<1,l,mid); bld(pos<<1|1,mid+1,r); add(d[pos],d[pos<<1]); add(d[pos],d[pos<<1|1]); } void chge(int pos,int l,int r) { if(l>=ql&&r<=qr) {add(qx,d[pos]);return;} int mid=(l+r)>>1; if(ql<=mid) chge(pos<<1,l,mid); if(qr>mid) chge(pos<<1|1,mid+1,r); } int dfn[maxn],low[maxn],dfn_clock,zz[maxn],inz[maxn],top; int bel[maxn],Ld[maxn],Rd[maxn],toth; void tj(int pos) { dfn[pos]=low[pos]=++dfn_clock; zz[++top]=pos;inz[pos]=1; int y,z,bot=top; for(y=fir[pos];y;y=nxt[y]) { if(inz[z=to[y]]) low[pos]=min(low[pos],dfn[z]); if(dfn[z]) continue; tj(z); low[pos]=min(low[pos],low[z]); } if(dfn[pos]==low[pos]) { ++toth; Ld[toth]=ld[zz[bot]]; Rd[toth]=rd[zz[bot]]; For(i,bot,top) { bel[zz[i]]=toth;inz[zz[i]]=0; Ld[toth]=min(Ld[toth],ld[zz[i]]); Rd[toth]=max(Rd[toth],rd[zz[i]]); } top=bot-1; } } void topsort() { int s=1,t=0,x,y,z; For(i,1,toth) if(!ind[i]) zz[++t]=i; while(s<=t) { x=zz[s++]; for(y=FIR[x];y;y=NXT[y]) if((--ind[z=TO[y]])==0) zz[++t]=z; } Rep(i,toth,1) { x=zz[i]; for(y=FIR[x];y;y=NXT[y]) { z=TO[y]; Ld[x]=min(Ld[x],Ld[z]); Rd[x]=max(Rd[x],Rd[z]); } } } int main() { read(n); For(i,1,n) read(p[i]),read(r[i]); bld(1,1,n); For(i,1,n) { ql=lower_bound(p+1,p+n+1,p[i]-r[i])-p; qr=upper_bound(p+1,p+n+1,p[i]+r[i])-p; qr--; qx=id[i]; chge(1,1,n); } tj(1); int x,y,z; For(i,1,tot) { x=bel[i]; for(y=fir[i];y;y=nxt[y]) { if((z=bel[to[y]])==x) continue; ADD(x,z); } } topsort(); For(i,1,n) { x=bel[id[i]]; ans+=(ll)i*(Rd[x]-Ld[x]+1)%mod; } printf("%lld ",ans%mod); return 0; }
Bob有一棵n个点的有根树,其中1号点是根节点。Bob在每个点上涂了颜色,并且每个点上的颜色不同。
定义一条路径的权值是:这条路径上的点(包括起点和终点)共有多少种不同的颜色。Bob可能会进行这几种操作:
1 x:把点x到根节点的路径上所有的点染上一种没有用过的新颜色。
2 x y:求x到y的路径的权值。
3 x y:在以x为根的子树中选择一个点,使得这个点到根节点的路径权值最大,求最大权值。
Bob一共会进行m次操作
$n,m leq 10^5$
为什么学长yyh和SDdalao xxy的代码都200+,那么长呢……
lct+线段树(dfs序)
注意这道题每次都是染一种没有用过的新颜色,而且路径直接到根
这种一般都是用线段树直接维护到根的路径,谁去树链剖分呢,查路径还多带一个log多不划算
lct里面同一个splay中的点的颜色相同。
1操作就是access,2操作是在线段树里面查x到根的+y到根的-lca到根的*2+1,而3是线段树查询最大值
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define ll long long #define db double #define For(i,a,b) for(int i=(a);i<=(b);++i) #define Rep(i,a,b) for(int i=(a);i>=(b);--i) #define lc son[pos][0] #define rc son[pos][1] const int maxn=1e5+7,maxt=23,W=19; int n,m,fa[maxn],son[maxn][2]; char cc;ll ff; template<typename T>void read(T& aa) { aa=0;ff=1; cc=getchar(); while(cc!='-'&&(cc<'0'||cc>'9')) cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } /////////////////////////////////////////////////tree int fir[maxn],nxt[2*maxn],to[2*maxn],e=0; void add(int x,int y) { to[++e]=y;nxt[e]=fir[x];fir[x]=e; to[++e]=x;nxt[e]=fir[y];fir[y]=e; } int dfn[maxn],end[maxn],p[maxn],dep[maxn],dfn_clock; int F[maxn][maxt]; void dfs(int pos,int f) { dfn[pos]=++dfn_clock; p[dfn_clock]=pos; fa[pos]=F[pos][0]=f; dep[pos]=dep[f]+1; For(i,1,W) F[pos][i]=F[F[pos][i-1]][i-1]; int y,z; for(y=fir[pos];y;y=nxt[y]) { if((z=to[y])==f) continue; dfs(z,pos); } end[pos]=dfn_clock; } int get_lca(int x,int y) { if(dep[x]!=dep[y]) { if(dep[x]<dep[y]) swap(x,y); Rep(i,W,0) if(dep[F[x][i]]>=dep[y]) x=F[x][i]; } if(x==y) return x; Rep(i,W,0) if(F[x][i]!=F[y][i]) { x=F[x][i]; y=F[y][i]; } return F[x][0]; } /////////////////////////////////////////////////segment tree int num[4*maxn],laz[4*maxn],ql,qr,qx; void ud(int pos) {num[pos]=max(num[pos<<1],num[pos<<1|1]);} void pd(int pos) { if(!laz[pos]) return; num[pos<<1]+=laz[pos]; num[pos<<1|1]+=laz[pos]; laz[pos<<1]+=laz[pos]; laz[pos<<1|1]+=laz[pos]; laz[pos]=0; } void bld(int pos,int l,int r) { if(l==r) { num[pos]=dep[p[l]]; return; } int mid=(l+r)>>1; bld(pos<<1,l,mid); bld(pos<<1|1,mid+1,r); ud(pos); } void chge(int pos,int l,int r) { if(l>=ql&&r<=qr) { num[pos]+=qx; laz[pos]+=qx; return; } int mid=(l+r)>>1; pd(pos); if(ql<=mid) chge(pos<<1,l,mid); if(qr>mid) chge(pos<<1|1,mid+1,r); ud(pos); } int q(int pos,int l,int r) { if(l>=ql&&r<=qr) return num[pos]; int mid=(l+r)>>1,rs=0; pd(pos); if(ql<=mid) rs=max(rs,q(pos<<1,l,mid)); if(qr>mid) rs=max(rs,q(pos<<1|1,mid+1,r)); return rs; } int Yth(int x,int y) { int lca=get_lca(x,y),rs=1; ql=qr=dfn[x]; rs+=q(1,1,n); ql=qr=dfn[y]; rs+=q(1,1,n); ql=qr=dfn[lca];rs-=2*q(1,1,n); return rs; } /////////////////////////////////////////////////lct bool isroot(int pos) {return son[fa[pos]][0]!=pos&&son[fa[pos]][1]!=pos;} void rotate(int pos) { int x,y,p; y=fa[x=fa[pos]]; p=son[x][1]==pos; if(!isroot(x)) son[y][son[y][1]==x]=pos; fa[pos]=y; son[x][p]=son[pos][!p]; fa[son[pos][!p]]=x; son[pos][!p]=x; fa[x]=pos; } void splay(int pos) { for(int x,y;!isroot(pos);rotate(pos)) { y=fa[x=fa[pos]]; if(!isroot(x)) (son[x][1]==pos^son[y][1]==x)? rotate(pos):rotate(x); } } int find(int pos) { while(lc) pos=lc; return pos; } void access(int pos) { for(int t=0,p;pos;pos=fa[t=pos]) { splay(pos); if(rc) { p=find(rc); ql=dfn[p]; qr=end[p]; qx=1; // printf("chge:%d,%d ",p,qx); chge(1,1,n); } if(t) { p=find(t); ql=dfn[p]; qr=end[p]; qx=-1; // printf("chge:%d,%d ",p,qx); chge(1,1,n); } rc=t; } } int main() { read(n); read(m); int op,x,y; For(i,1,n-1) { read(x); read(y); add(x,y); } dfs(1,0); bld(1,1,n); For(i,1,m) { read(op); read(x); if(op==1) access(x); else if(op==2) read(y),printf("%d ",Yth(x,y)); else ql=dfn[x],qr=end[x],printf("%d ",q(1,1,n)); } return 0; }
二分然后随便用啥子数据结构维护一下
可以当作线段树合并模板题。
不知道为什么要拿一个线段树来维护所有线段树,我懒,直接set维护算啦,代码短好多呐
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> #include<set> using namespace std; #define ll long long #define db double #define For(i,a,b) for(int i=(a);i<=(b);++i) #define Rep(i,a,b) for(int i=(a);i>=(b);--i) #define lc son[pos][0] #define rc son[pos][1] const int maxn=1e5+7,maxm=1e7+7; const ll Bs=25,U=(1<<25)-1; int n,m,root[maxn],fl[maxn],zz[maxn],t; set<int> G; set<int>::iterator it; char cc;ll ff; template<typename T>void read(T& aa) { aa=0;ff=1; cc=getchar(); while(cc!='-'&&(cc<'0'||cc>'9')) cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } ll pr(ll x,ll y) {return (x<<Bs)+y;} ll fi(ll x) {return x>>Bs;} ll se(ll x) {return x&U;} int sum[maxm],son[maxm][2],ql,qr,qx,tot; void ud(int pos) {sum[pos]=sum[lc]+sum[rc];} void bld(int&pos,int l,int r) { if(!pos) pos=++tot; sum[pos]+=qx; if(l==r) return; int mid=(l+r)>>1; if(ql<=mid) bld(lc,l,mid); else bld(rc,mid+1,r); } void merge(int&pos,int p,int l,int r) { if((ll)pos*p==0) {pos=pos+p;return;} if(l==r) {sum[pos]+=sum[p];return;} int mid=(l+r)>>1; merge(lc,son[p][0],l,mid); merge(rc,son[p][1],mid+1,r); ud(pos); } ll split(int pos,int l,int r,int k) { int p=++tot,mid=(l+r)>>1; ll x; if(sum[lc]==k) {son[p][1]=rc; rc=0;} else if(sum[lc]<k) { x=split(rc,mid+1,r,k-sum[lc]); rc=fi(x); son[p][1]=se(x); } else { x=split(lc,l,mid,k); lc=fi(x); son[p][0]=se(x); son[p][1]=rc; rc=0; } ud(pos); ud(p); return pr(pos,p); } void get_split(int pos,int x) { if(x==0||x==sum[root[pos]]) return; ll p; if(!fl[pos]) p=split(root[pos],1,n,x); else { p=split(root[pos],1,n,sum[root[pos]]-x); p=pr(se(p),fi(p)); root[pos]=fi(p); } root[pos+x]=se(p); fl[pos+x]=fl[pos]; G.insert(pos+x); } int q(int pos,int l,int r) { if(l==r) return l; int mid=(l+r)>>1; if(qx<=sum[lc]) return q(lc,l,mid); qx-=sum[lc]; return q(rc,mid+1,r); } int get_ans(int x) { int pos; for(it=G.begin();it!=G.end();++it) { if(x<=sum[root[*it]]) break; x-=sum[root[*it]]; } pos=*it; if(!fl[pos]) qx=x; else qx=sum[root[pos]]-x+1; return q(root[pos],1,n); } /////////////////////////////////////////////////debug void dfs(int pos,int l,int r) { if(!pos||(!sum[pos])) return; if(l==r) {printf(" %d",l);return;} int mid=(l+r)>>1; dfs(lc,l,mid); dfs(rc,mid+1,r); } void debug() { printf("G: "); for(it=G.begin();it!=G.end();++it) { printf("%d(fl=%d):",*it,fl[*it]); dfs(root[*it],1,n); printf(" "); } printf(" "); } ///////////////////////////////////////////////// int main() { read(n); read(m); ll op,x,y,ld,rd; For(i,1,n) root[i]=++tot,G.insert(i); G.insert(n+1); For(i,1,n) read(x),ql=qr=x,qx=1,bld(root[i],1,n); For(i,1,m) { read(op); read(x); read(y); it=G.upper_bound(x); --it; ld=*it; get_split(ld,x-ld); it=G.upper_bound(y); --it; rd=*it; get_split(rd,y-rd+1); t=0; for(it=G.lower_bound(x);*it<=rd;++it) zz[++t]=*it; For(i,2,t) merge(root[zz[1]],root[zz[i]],1,n); it=G.upper_bound(x); while(*it<=rd) G.erase(it),it=G.upper_bound(x); fl[x]=op; // debug(); } read(x); printf("%d ",get_ans(x)); return 0; }
小 (C) 有一棵 (n) 个结点的有根树,根是 (1) 号结点,且每个结点最多有两个子结点。
定义结点 (x) 的权值为:
1.若 (x) 没有子结点,那么它的权值会在输入里给出,保证这类点中每个结点的权值互不相同。
2.若 (x) 有子结点,那么它的权值有 (p_x) 的概率是它的子结点的权值的最大值,有 (1-p_x) 的概率是它的子结点的权值的最小值。
现在小 (C) 想知道,假设 (1) 号结点的权值有 (m) 种可能性,权值第 (i) 小的可能性的权值是 (V_i) ,它的概率为 (Di(Di>0)) ,求:
[displaystyle sum _{i=1} ^ {m} i cdot V_i cdot D_i^2]
你需要输出答案对 (998244353) 取模的值。
对于 (40\%) 的数据,有 (1leq nleq 5000) ;
对于 (100\%) 的数据,有 (1leq nleq 3 imes 10^5, 1leq w_ileq 10^9)。
线段树合并优化dp,sb真的错误太多。一定要记得pd和ud啊
//Serene #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define ll long long #define db double #define For(i,a,b) for(int i=(a);i<=(b);++i) #define Rep(i,a,b) for(int i=(a);i>=(b);--i) #define lc son[pos][0] #define rc son[pos][1] const ll mod=998244353,R=796898467; const int maxn=1e6+7,maxm=2e7+7; ll n,fa[maxn],tson[maxn],v[maxn],p[maxn],TOT; int troot; char cc;ll ff; template<typename T>void read(T& aa) { aa=0;ff=1; cc=getchar(); while(cc!='-'&&(cc<'0'||cc>'9')) cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } int fir[maxn],nxt[maxn],to[maxn],e=0; void add(int x,int y) { // printf("add:%d->%d ",x,y); to[++e]=y;nxt[e]=fir[x];fir[x]=e; } int root[maxn],son[maxm][2],tot; ll sum[maxm],laz[maxm],ql,qr,qx; void ud(int pos) {sum[pos]=(sum[lc]+sum[rc])%mod;} void add_laz(int pos,ll x) { laz[pos]=laz[pos]*x%mod; sum[pos]=sum[pos]*x%mod; } void pd(int pos) { if(laz[pos]==1) return; add_laz(lc,laz[pos]); add_laz(rc,laz[pos]); laz[pos]=1; } void bld(int& pos,int l,int r) { if(!pos) pos=++tot,laz[pos]=1; sum[pos]+=qx; if(l==r) return; int mid=(l+r)>>1; if(ql<=mid) bld(lc,l,mid); if(qr>mid) bld(rc,mid+1,r); } void get_laz(int pos,int p,int l,int r,ll x,ll y) { if((!pos)||(!p)||(l==r)) { add_laz(pos,x); add_laz(p,y); return; } int mid=(l+r)>>1; pd(pos); pd(p); ll l1=sum[lc],r1=sum[rc],l2=sum[son[p][0]],r2=sum[son[p][1]]; get_laz(lc,son[p][0],l,mid,(x+r2*(1-qx+mod))%mod,(y+r1*(1-qx+mod))%mod); get_laz(rc,son[p][1],mid+1,r,(x+l2*qx)%mod,(y+l1*qx)%mod); ud(pos); ud(p); } void merge(int&pos,int p,int l,int r) { if((!pos)||(!p)) {pos=pos+p;return;} if(l==r) {sum[pos]+=sum[p];return;} pd(pos); pd(p); int mid=(l+r)>>1; merge(lc,son[p][0],l,mid); merge(rc,son[p][1],mid+1,r); ud(pos); } void get_ans(int pos) { if(pos==0||tson[pos]==0) return; int ls=to[fir[pos]],rs=to[nxt[fir[pos]]]; get_ans(ls); get_ans(rs); qx=v[pos]; if(ls==0||rs==0) root[pos]=root[ls+rs]; else { get_laz(root[ls],root[rs],1,TOT,0,0); merge(root[ls],root[rs],1,TOT); } root[pos]=root[ls]; } ll cal(int pos,int l,int r) { if(l==r) return (ll)l*sum[pos]%mod*sum[pos]%mod*p[l]%mod; pd(pos); int mid=(l+r)>>1; return (cal(lc,l,mid)+cal(rc,mid+1,r))%mod; } int main() { read(n); read(fa[1]); For(i,2,n) { read(fa[i]); add(fa[i],i); ++tson[fa[i]]; } For(i,1,n) { read(v[i]); if(!tson[i]) p[++TOT]=v[i]; } sort(p+1,p+TOT+1); For(i,1,n) { if(!tson[i]) v[i]=lower_bound(p+1,p+TOT+1,v[i])-p; else v[i]=v[i]*R%mod; } laz[0]=1; For(i,1,n) if(!tson[i]) ql=qr=v[i],qx=1,bld(root[i],1,TOT); get_ans(1); printf("%lld ",cal(root[1],1,TOT)); return 0; }