线段树、主席树

ctsc2018的D2T1(主席树模板题),大家都半个小时AC了,我因为一个sb bug调了2个多小时……

博主是个大sb。

 

bzoj2653 middle

一个长度为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;
}

 

bzoj3524 Couriers

给一个长度为$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;
}

 

bzoj3585 mex

有一个长度为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;
}

 

bzoj3123 森林

小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;
}

  

rehealsal

给出$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$,只需要将前缀和改为可持久化权值线段树即可

 

bzoj2333 棘手的操作

有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;
}

 

bzoj5017 炸弹

在一条直线上有 $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;
}

  

bzoj4817 树点涂色

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;
}

 

bzoj4552 排序

给出一个1到n的全排列,现在对这个全排列序列进行m次局部排序,排序分为两种:
(0,l,r)表示将区间[l,r]的数字升序排序
(1,l,r)表示将区间[l,r]的数字降序排序
最后询问第q位置上的数字。

二分然后随便用啥子数据结构维护一下

可以当作线段树合并模板题。

不知道为什么要拿一个线段树来维护所有线段树,我懒,直接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;
}

 

loj2537Minimax

\(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;
}

  

posted @ 2018-05-27 11:34  shixinyi  阅读(285)  评论(0编辑  收藏  举报