普通平衡树

SPLAY

#include<cstdio>
#include<iostream>
#include<cstring>
#define N 1000005
using namespace std;
int ch[N][2],f[N],size[N],cnt[N],key[N],root,sz;
inline void clear(int x){
  ch[x][0]=ch[x][1]=f[x]=size[x]=cnt[x]=key[x]=0;
}
inline bool get(int x){
  return ch[f[x]][1]==x;
}
inline void update(int x){
    if(x){
      size[x]=cnt[x];
      if(ch[x][0]) size[x]+=size[ch[x][0]];
      if(ch[x][1]) size[x]+=size[ch[x][1]];
    }
}
inline void rotate(int x){
    int p=f[x],g=f[p],wz=get(x);
    ch[p][wz]=ch[x][wz^1];
    if(ch[p][wz]) f[ch[p][wz]]=p;
    ch[x][wz^1]=p;
    f[p]=x; f[x]=g;
    if(g) ch[g][ch[g][1]==p]=x;
    update(p); update(x);
}
inline void splay(int x)
{
       for(int fa;fa=f[x];rotate(x))
         if(f[fa])
           rotate((get(x)==get(fa))?fa:x);
       root=x;
}
inline void insert(int x){
    if(root==0){
      sz++; ch[sz][0]=ch[sz][1]=f[sz]=0;
      size[sz]=cnt[sz]=1; key[sz]=x; root=sz;
      return ;
    }
    int now=root,fa=0;
    while(1){
      if(x==key[now]){
        cnt[now]++;update(now); 
        update(fa); splay(now);
        break;
      }
      fa=now; now=ch[fa][key[fa]<x];
      if(now==0){
        sz++; f[sz]=fa; ch[sz][0]=ch[sz][1]=0;
        ch[fa][key[fa]<x]=sz; size[sz]=cnt[sz]=1;
        key[sz]=x; update(fa); splay(sz);
        break;
      }
    }
}
inline int rank(int x){
    int now=root,ans=0;
    while(1){
      if(x<key[now])
        now=ch[now][0];
      else{
        ans+=(ch[now][0]?size[ch[now][0]]:0);
        if(x==key[now]){ splay(now); return ans+1; }
        ans+=cnt[now]; now=ch[now][1];
      }
    }
}
inline int find(int x){
    int now=root;
    while(1){
      if(ch[now][0]&&x<=size[ch[now][0]]) now=ch[now][0];
      else{
        int temp=(ch[now][0]?size[ch[now][0]]:0)+cnt[now];
        if(x<=temp) return key[now];
        x-=temp; now=ch[now][1];
      }
    }
}
inline int pre(){
     int now=ch[root][0];
     while(ch[now][1]) now=ch[now][1];
     return now;
}
inline int next(){
     int now=ch[root][1];
     while(ch[now][0]) now=ch[now][0];
     return now;
}
inline void del(int x){
    int num=rank(x);
    if(cnt[root]>1){cnt[root]--; size[root]--; return;}
    if(!ch[root][0]&&!ch[root][1]){clear(root); root=0; return ;}
    if(!ch[root][0]){
      int old=root; root=ch[root][1];
      f[root]=0; clear(old);
      return ;
    }
    if(!ch[root][1]){
      int old=root; root=ch[root][0];
      f[root]=0; clear(old);
      return ;
    }
    int big=pre(),old=root; splay(big);
    ch[root][1]=ch[old][1]; f[ch[root][1]]=root;
    clear(old); update(root);
}
int main()
{
    int n,opt,x;
    scanf("%d",&n);
    while(n--)
    {
      scanf("%d%d",&opt,&x);
      if(opt==1) insert(x);
      if(opt==2) del(x);
      if(opt==3) printf("%d\n",rank(x));
      if(opt==4) printf("%d\n",find(x));
      if(opt==5){
        insert(x); 
        printf("%d\n",key[pre()]);
        del(x);
      }
      if(opt==6){
        insert(x); 
        printf("%d\n",key[next()]);
        del(x);
      }
    }
    return 0;
}

TREAP

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <set>
#include <vector>
#include <ctime>
#define size(a) ((a!=NULL)?(a)->size:0)
using namespace std;
struct Node{
    Node *ch[2];
    int key,size,num,tot;
    Node(int x=0){
        key=rand();num=x;size=tot=1;
        memset(ch,0,sizeof ch);
    }
    void* operator new (size_t);
    void operator delete (void *p);
}*root,*C,*M;

vector<Node*> q;

void* Node :: operator new (size_t){
    if(C==M){
        C=new Node[1<<15];
        M=C+(1<<15);
    }
    return C++;
}
void Node :: operator delete (void *p){
    q.push_back((Node*)p);
}
void pushup(Node *now){
    if(!now) return;
    now->size=size(now->ch[0])+size(now->ch[1])+now->tot;
}
void rot(Node *&now,int wh){
    Node *son=now->ch[wh];
    now->ch[wh]=son->ch[wh^1]; pushup(now);
    son->ch[wh^1]=now; pushup(son);
    now=son;
}
void insert(Node *&now,int x){
    if(!now){
        now=new Node(x);
        return ;
    }
    if(now->num==x){now->size+=1;now->tot+=1;return;}
    int wh=now->num<x;
    insert(now->ch[wh],x);
    pushup(now);
    if(now->ch[wh]->key<now->key) rot(now,wh);
}
void del(Node *&now,int x){
    if(now->num==x){
        if(now->tot>1){now->size-=1;now->tot-=1;return;}
        if(!now->ch[0]&&!now->ch[1]){delete now;now=NULL;}
        else if(!now->ch[0]){
            Node *t=now;
            now=now->ch[1];
            delete t; t=NULL;
        }
        else if(!now->ch[1]){
            Node *t=now;
            now=now->ch[0];
            delete t; t=NULL;
        }
        else{
            int wh=now->ch[0]->key>now->ch[1]->key;
            rot(now,wh); del(now,x);
        }
    }
    else{
        if(now->num>x)del(now->ch[0],x);
        else del(now->ch[1],x);
    }
    if(now) pushup(now);
}
int Rank(int x){
    int ans=1; Node *now=root;
    while(now){
        if(now->num<x){
            ans+=size(now->ch[0])+now->tot;
            now=now->ch[1];
        }
        else if(now->num==x){ans+=size(now->ch[0]);break;}
        else now=now->ch[0];
    }
    return ans;
}
int kth(int x){
    Node *now=root; int all=x;
    while(now){
        if(size(now->ch[0])>=all) {now=now->ch[0];continue;}
        all-=size(now->ch[0]);
        if(now->tot>=all) return now->num;
        all-=now->tot;
        now=now->ch[1];
    }
}
int pre(int x){return kth(Rank(x)-1);}
int next(int x){return kth(Rank(x+1));}
int main()
{
    int n,opt,x; srand(time(NULL));
    scanf("%d",&n);
    while(n--)
    {
        scanf("%d%d",&opt,&x);
        switch(opt){
            case 1: insert(root,x); break;
            case 2: del(root,x); break;
            case 3: printf("%d\n",Rank(x)); break;
            case 4: printf("%d\n",kth(x)); break;
            case 5: printf("%d\n",pre(x)); break;
            case 6: printf("%d\n",next(x)); break;
        }
    }
}

无旋TREAP

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<ctime>
using namespace std;
#define size(x) ((x!=NULL)?(x->size):(0))
#define tp pair<Treap *,Treap *>
struct Treap{
    Treap *ch[2];
    int key,val,size;
    Treap(int x){
        size=1;val=x;key=rand(); 
        ch[0]=ch[1]=NULL;
    }
    inline void pushup(){size=1+size(ch[0])+size(ch[1]);}
}*root;
Treap *merge(Treap *a,Treap *b){
    if(!a)return b;
    if(!b)return a;
    if(a->key < b->key){
        a->ch[1]=merge(a->ch[1],b);
        a->pushup(); return a;
    }
    else{
        b->ch[0]=merge(a,b->ch[0]);
        b->pushup(); return b;
    }
}
tp split(Treap *x,int k){
    if(x==NULL)return tp(NULL,NULL);
    tp y;
    if(size(x->ch[0])>=k){
        y=split(x->ch[0],k);
        x->ch[0]=y.second;
        x->pushup(); 
        y.second=x;
    }
    else{
        y=split(x->ch[1],k-size(x->ch[0])-1);
        x->ch[1]=y.first;
        x->pushup();y.first=x;
    }
    return y;
}
int getrank(Treap *x,int k){
    if(x==NULL)return 0;
    if(x->val>=k)
        return getrank(x->ch[0],k);
    else return getrank(x->ch[1],k)+size(x->ch[0])+1;
}
int getkth(int k){
    tp x=split(root,k-1);
    tp y=split(x.second,1);
    Treap *ans=y.first;
    root=merge(x.first,merge(ans,y.second));
    return ans!=NULL?ans->val:0;
}
void insert(int x){
    int k=getrank(root,x);
    tp y=split(root,k);
    Treap *rt=new Treap(x);
    root=merge(merge(y.first,rt),y.second);
}
void delet(int x){
    int k=getrank(root,x);
    tp y=split(root,k);
    tp z=split(y.second,1);
    root=merge(y.first,z.second);
}
void print(Treap *x){
    printf("%d  %d\n",x->key,x->val);
    if(x->ch[0])print(x->ch[0]);
    if(x->ch[1])print(x->ch[1]);
}
int main(){
    int m; //srand(time(NULL));
    scanf("%d",&m);
    for(int i=1,opt,x;i<=m;i++){
        scanf("%d%d",&opt,&x);
        if(opt==1)insert(x);
        if(opt==2)delet(x);
        if(opt==3)printf("%d\n",getrank(root,x)+1);
        if(opt==4)printf("%d\n",getkth(x));
        if(opt==5)printf("%d\n",getkth(getrank(root,x)));
        if(opt==6)printf("%d\n",getkth(getrank(root,x+1)+1));
        if(opt==7)print(root);
    }
    return 0;
}

一种在 O(n34logn) O ( n 3 4 log ⁡ n ) 的时间中求出大多数积性函数的前缀和的方法.

引入

i=1nF(i) ∑ i = 1 n F ( i )

其中 F(x) F ( x ) 是一个积性函数. 要求在低于线性的时间内求出.

转化
如果 F(x) F ( x ) 是一个十分特殊的积性函数, 比如 μ2(x) μ 2 ( x ) , 那么可以非常快地求出; 就算它不那么特殊, 如果我们能找到两个合适的函数 G,H G , H 满足 FG=H F ∗ G = H , 并且 G,H G , H 的前缀和容易求出, 那么可以利用杜教筛在 O(n23) O ( n 2 3 ) 的时间内求出. 但是当 F(x) F ( x ) 不具备这些性质的时候呢?

当然了, 还是有一个要求: 当 p p 为质数的时候, F(pc) 是一个关于 p p 的低阶多项式.

我们将 [1,n] 的所有数按照是否有 >n > n 的质因子分为两类, 那么显然有:

i=1nF(i)=1ini have no prime factors >nF(i)1+n<jnij is primeF(j) ∑ i = 1 n F ( i ) = ∑ 1 ≤ i ≤ n i  have no prime factors  > n F ( i ) ( 1 + ∑ n < j ≤ ⌊ n i ⌋ j  is prime F ( j ) )

由于 in i ≥ n 时后面一部分等于 1 1 , 所以需要计算以下两个东西:

对于每个 1i<n, 计算

n<jnij is primeF(j) ∑ n < j ≤ ⌊ n i ⌋ j  is prime F ( j )

nini have no prime factors >nF(i) ∑ n ≤ i ≤ n i  have no prime factors  > n F ( i )

当然, 最后还要用线性筛求出 [1,n) [ 1 , n ) F(x) F ( x ) 乘上相应的系数对答案的贡献, 这一部显然不会成为瓶颈.

Part 1
gk(i,j) g k ( i , j ) 表示 [1,j] [ 1 , j ] 中与前 i i 个质数互质的数的 k 次幂和. 显然有转移

gk(i,j)=gk(i1,j)pkigk(i1,jpi) g k ( i , j ) = g k ( i − 1 , j ) − p i k g k ( i − 1 , ⌊ j p i ⌋ )

观察到 j j 的取值只有 n 种, 于是直接暴力计算的复杂度为 O(nlogn) O ( n log ⁡ n ) .

考虑优化. 首先注意到当 pi+1>j p i + 1 > j gk(i,j)=1 g k ( i , j ) = 1 , 但是这个观察并不能带来什么优化; 紧接着我们发现如果 pi>jpi p i > ⌊ j p i ⌋ p2i>j p i 2 > j 时, gk(i,j) g k ( i , j ) 的转移变为:

gk(i,j)=gk(i1,j)pki g k ( i , j ) = g k ( i − 1 , j ) − p i k

我们从小到大枚举 i i , 对于某个 j 一旦 p2i0>j p i 0 2 > j 便可以不再转移, 之后如果其他的值需要使用到它在 i1 i 1 时的值, 直接用 gk(i0,j)i11l=i0pkl g k ( i 0 , j ) − ∑ l = i 0 i 1 − 1 p l k 即可. (其实这个地方还有一些细节, 可以自己思考)

此时的复杂度可以简单地用积分近似为 O(n34logn) O ( n 3 4 log ⁡ n ) .

Part 2
f(i,j) f ( i , j ) 表示 [1,j] [ 1 , j ] 中仅由前 i i 个质数组成的数的 F(x) 之和. 显然有转移

f(i,j)=f(i1,j)+c1F(pci)f(i1,jpci) f ( i , j ) = f ( i − 1 , j ) + ∑ c ≥ 1 F ( p i c ) f ( i − 1 , ⌊ j p i c ⌋ )

虽然多出来一个 c1 ∑ c ≥ 1 , 但是直接暴力计算的复杂度仍然是 O(nlogn) O ( n log ⁡ n ) . 如何优化? 似乎不太能沿用之前的方法了.

但是如果将状态定义中的”前 i i 个质数”改为”(小于 n)的后 i i 个质数”, 此时当 pi>j 时, 一定有 f(i,j)=1 f ( i , j ) = 1 . 类似地, 当 p2i>j p i 2 > j 时转移变为:

f(i,j)=f(i1,j)+F(pi) f ( i , j ) = f ( i − 1 , j ) + F ( p i )

所以可以从大到小枚举 i i , 如果对于某个 j p2i>j p i 2 > j , 可以不转移, 每次用的时候加入 [pi,min(j,n)] [ p i , min ( j , n ) ] 这一段的质数的 F(p) F ( p ) 就可以了.

类似上面的分析, 复杂度为 O(n34logn) O ( n 3 4 log ⁡ n ) .

小结
从上面的推导可以看出, 如果 F(pc) F ( p c ) 是一个关于 p p 的常数阶多项式, 那么我们可以在 O(n34logn) 的时间内求出 F(x) F ( x ) 的前 n n 项和. 利用滚动数组, 空间复杂度是 O(n) 的.

主要用到了以下几个想法:

将所有数分按照有无大于 n n 的质因子为两类
整除的结果只有 O(n) O ( n )
一个关于递推的优化技巧
这几个想法相对独立, 都挺有启发性的, 感觉甚至可以优化一些 DP.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值