线段树、主席树
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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",p,qx);
chge(1,1,n);
}
if(t) {
p=find(t);
ql=dfn[p]; qr=end[p]; qx=-1;
// printf("chge:%d,%d\n",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\n",Yth(x,y));
else ql=dfn[x],qr=end[x],printf("%d\n",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:\n");
for(it=G.begin();it!=G.end();++it) {
printf("%d(fl=%d):",*it,fl[*it]);
dfs(root[*it],1,n); printf("\n");
}
printf("\n");
}
/////////////////////////////////////////////////
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\n",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\%\) 的数据,有 \(1\leq n\leq 5000\) ;
对于 \(100\%\) 的数据,有 \(1\leq n\leq 3\times 10^5, 1\leq w_i\leq 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\n",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\n",cal(root[1],1,TOT));
return 0;
}

浙公网安备 33010602011771号