五一 DAY2

 DAY 2       2019.4.29


 比如:依次输入 3 1 5 4 2                                        插入 6

 

这里FZdalao有一个很巧妙的构造,直接吧输入的数字排成一个二叉搜索树,方便以后操作

把mid作为根节点,两边排

void build2()      //精巧的构造,使得树高是log N的
{
        sort(a+1,a+n+1);
        root=Divide(1,n);
        tot=n;
}

int Divide(int l,int r)
{
         if (l>r) return 0;
        ls[mid]=Divide(l,mid-1);    //以mid为根节点 构造二叉搜索树 
        rs[mid]=Divide(mid+1,r);
        fa[ls[mid]]=fa[rs[mid]]=mid; fa[0]=0;
        sum[mid]=a[mid];
        size[mid]=size[ls[mid]]+size[rs[mid]]+1;  //树的大小 
        return mid;
}

 

 

说白了,删掉一个数,就用这个数左子树最大值或是右子树最小值替换他,并把父辈祖辈size-1

size[x]表示x这个节点子树里节点的个数 

1.简单情况

int id=Find(x),t=fa[id];//找到这个点的编号 
        if (!ls[id]&&!rs[id])   //id没有孩子 
        {
                if (ls[t]==id) ls[t]=0; else rs[t]=0; //去掉儿子边
                for (i=id;i;i=fa[i]) size[i]--;   //id自己连同他的父辈大小全部-1 
        }

2.id有一个孩子        我们删除7

    else
        if (!ls[id]||!rs[id])   //id有一个孩子 
        {
                int child=ls[id]+rs[id];//找存在的儿子的编号 
                if (ls[t]==id) ls[t]=child; else rs[t]=child;  //把id的儿子过继为父亲的儿子 
                fa[child]=t;
                for (i=id;i;i=fa[i]) size[i]--;    //id自己连同他的父辈大小全部-1 
        }

 3.他有两个孩子

else //最复杂的情况         
 {
    for (i=fa[y];i;i=fa[i]) size[i]--;//注意到变换完之后y到root路径上每个点的size都减少了1
    int tt=fa[y]; //先把y提出来 
    if (ls[tt]==y)
    {
       ls[tt]=rs[y];
       fa[rs[y]]=tt;
    }                    
    else
    {
       rs[tt]=rs[y];
       fa[rs[y]]=tt;
    }    
//再来提出x if (ls[t]==x) { ls[t]=y; fa[y]=t; ls[y]=ls[id]; rs[y]=rs[id]; } else { rs[t]=y; fa[y]=t; ls[y]=ls[id]; rs[y]=rs[id]; }
size[y]
=size[ls[y]]+size[rs[y]]+1;//更新一下size }

int Findkth(int now,int k)
{
        if (size[rs[now]]>=k) return Findkth(rs[now],k);
        else if (size[rs[now]]+1==k) return sum[now];
        else Findkth(ls[now],k-size[rs[now]]-1);//注意到递归下去之后右侧的部分都比它要大 
}

中序遍历,左头右

每走到一个节点,先找左儿子,直到没有,再回到根(当前),再找右儿子

 

 

 


 

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<ctime>
#include<set>
#include<vector>
#include<map>
#include<queue>

#define N 300005
#define M 8000005

#define ls (t<<1)
#define rs ((t<<1)|1)
#define mid ((l+r)>>1)

#define mk make_pair
#define pb push_back
#define fi first
#define se second

using namespace std;

int i,j,m,n,p,k,a[N];

int FindMin()   //找最小值 
{
        return a[1];
}

void build1()   
{
        sort(a+1,a+n+1);
}

void up(int now)   //上浮操作 
{
        while (now&&a[now]<a[now/2]) swap(a[now],a[now/2]),now/=2;
}

void ins(int x)   //加入新数 
{
        a[++n]=x; up(n);
}

void down(int now)  //下沉操作 
{
        while (now*2<=n)   //保证不会超出树的规模 
        {
                if (now*2==n)
                {
                        if (a[now]>a[now*2]) swap(a[now],a[now*2]),now*=2; 
                }
                else
                {
                        if (a[now]<=a[now*2]&&a[now]<=a[now*2+1]) break;
                        if (a[now*2]<a[now*2+1]) swap(a[now],a[now*2]),now*=2;  //与最小的儿子交换 
                        else swap(a[now],a[now*2+1]),now=now*2+1; 
                }
        }
}

void del(int x)   //删除 
{
        swap(a[x],a[n]); --n;
        up(x);
        down(x);
}

void change(int x,int val)    //交换数值 ,小则上浮,大则下沉 
{
        if (a[x]>val)
        {
            a[x]=val;
            up(x);
        }
        else
        {
            a[x]=val;
            down(x);
        }
}

void build2()
{
        for (i=n/2;i>=1;--i) down(i);
}

int main()
{ 
     scanf("%d",&n);
     for (i=1;i<=n;++i) scanf("%d",&a[i]);
     build2();
}
    

 

 

(这是个大根堆)

      

 

set<int>st;   

st.insert(x);     //插入 
st.erase(x);      //删除 
st.fnd(x);        //寻找 
st.clear(x)       //清除 

st.lower_bound(x);//  第一个>= x 的数的迭代器    
st.upper_bound(x);//  第一个> x 的数的迭代器   
st.begin(x);      //起点
st.end(x) ;       //终点 
                  //只有以上这四个有迭代器, 因为他们是位置 



set<int>::iterater it=st.lower_bound(x);   //迭代器 
             ++it;//右移
             --it;//左移
             
int x=*it;     //加星号,返回迭代器指向的实际数值


PART 1

       

       所以ST表就是吧一个区间化解成两个区间,这两个区间合起来,保证全覆盖原区间

这两个区间可以有重叠

       解决问题做到  重但不漏

 为什么要先求出每个[ i , i + 2k )的最值?

因为你要把一个问题化为两个,并且你在预处理的时候并不知道所给的区间有多大

证明一下全覆盖:

设 k 为最大的保证 2r - l + 1 

那么 2k+1 ≤ + 1

实现了全覆盖

若  2k+1  >= + 1

 那么k不是最大的,与假设相反

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<ctime>
#include<set>
#include<vector>
#include<map>
#include<queue>

#define N 300005
#define M 8000005
#define K 18 

#define ls (t<<1)
#define rs ((t<<1)|1)
#define mid ((l+r)>>1)

#define mk make_pair
#define pb push_back
#define fi first
#define se second

using namespace std;

int i,j,m,n,p,k,ST[K+1][N],a[N],Log[N];

int Find(int l,int r)
{
        int x=Log[r-l+1];
        return max(ST[x][l],ST[x][r-(1<<x)+1]); //注意到对于[l,r],[l,l+2^x-1],[r-2^x+1,r]并起来是[l,r] 
}

int main()
{
        scanf("%d",&n);
        for (i=1;i<=n;++i) scanf("%d",&a[i]);
        for (i=1;i<=n;++i) ST[0][i]=a[i];
        for (i=1;i<=K;++i)
            for (j=1;j+(1<<i)-1<=n;++j)
                    ST[i][j]=max(ST[i-1][j],ST[i-1][j+(1<<(i-1))]); //ST[i][j]为从j开始的长度为2^i的区间的最大值 
                                                        //显然[j,j+2^i)=[j,j+2^(i-1))+[j+2^(i-1),j+2^i)=max(ST[i-1][j],ST[i-1][j+2^(i-1)])
        for (i=1;(1<<i)<N;++i) Log[1<<i]=i; //令Log[x]为比x小的最大的2^y 
        for (i=1;i<N;++i) if (!Log[i]) Log[i]=Log[i-1];
        printf("%d
",Find(1,3));
}

 PART 2

 

 

 

 

[4,5]不需要继续拆

#include<cstdio>
#include<algorithm>

#define N 100005

#define ls (t<<1)
#define rs ((t<<1)|1)
#define mid ((l+r)>>1)

int tree[N*4];
//
T当前节点编号,l,r大区间长,tree标记为终止节点
void modify(int ll,int rr,int c,int l,int r,int t)  //单点修改,看是往左走还是游走,或是叶子节点 
{
        if (ll<=l&&r<=rr) 
        {
                tree[t]=c;
                return;
        }
        if (ll<=mid) modify(ll,rr,c,l,mid,ls);
        if (rr>mid)  modify(ll,rr,c,mid+1,r,rs);
        tree[t]=tree[ls]+tree[rs];
}
 
int S;

void ask(int ll,int rr,int l,int r,int t)//[ll,rr]是要分解的区间 当前正分解到的节点编号是t,区间范围是[l,r] 
{
        if (ll<=l&&r<=rr)    //出现区间[l,r]被[ll,rr]包含 
        {
                S+=tree[t];
                return;
        }
        if (ll<=mid) ask(ll,rr,l,mid,ls);   //走左边 
        if (rr>mid)  ask(ll,rr,mid+1,r,rs); //走右边 
}

int main()
{
        modify(l,l,c,1,n,1);   //递归分解l,r
        S=0;
        ask(l,r,1,n,1);
}

 

 

 

 

先把标记放在一个大区间

要继续向下拆分,就把标记传给儿子

先不下传

只有在需要用到的时候再下传子区间

#include<cstdio>
#include<algorithm>

#define N 100005

#define ls (t<<1)
#define rs ((t<<1)|1)
#define mid ((l+r)>>1)

int tree[N*4],Inc[N*4];

void down(int t,int len) //下传点区间编号是t,长度是len 
{
        //左儿子区间长度(len+1)/2,右儿子区间长度(len/2) 
        if (!Inc[t]) return;
        Inc[ls]+=Inc[t];    //区间整体加C

        tree[ls]+=Inc[t]*((len+1)>>1);
        Inc[rs]+=Inc[t];
        tree[rs]+=Inc[t]*(len>>1);
        Inc[t]=0;
}

void modify(int ll,int rr,int c,int l,int r,int t)//[ll,rr]是要分解的区间 当前正分解到的节点编号是t,区间范围是[l,r] 
{
        if (ll<=l&&r<=rr) //当它是一个终止节点的时候,更新Inc标记和sum的值 
        {
                tree[t]=c*(r-l+1);     //如果是整个区间被包含在内,就直接加inc,否则下传儿子down

                Inc[t]+=c;
                return;
        }
        down(t,r-l+1);
        if (ll<=mid) modify(ll,rr,c,l,mid,ls);
        if (rr>mid)  modify(ll,rr,c,mid+1,r,rs);
        tree[t]=tree[ls]+tree[rs];
}
 
int S;

void ask(int ll,int rr,int l,int r,int t)//[ll,rr]是要分解的区间 当前正分解到的节点编号是t,区间范围是[l,r] 
{
        if (ll<=l&&r<=rr)
        {
                S+=tree[t];
                return;
        }
        down(t,r-l+1);    //下传操作
        if (ll<=mid) ask(ll,rr,l,mid,ls);
        if (rr>mid)  ask(ll,rr,mid+1,r,rs);
}

int main()
{
        modify(l,r,c,1,n,1);
        S=0;
        ask(l,r,1,n,1);
}

一共10000个砖块,20000个端点,其实可以看作40000块砖

看成4n-1块砖,n表示地毯数目

 

 

 

树状数组只能求前缀和

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<ctime>
#include<set>
#include<vector>
#include<map>
#include<queue>

#define N 300005
#define M 8000005

#define ls (t<<1)
#define rs ((t<<1)|1)
#define mid ((l+r)>>1)

#define mk make_pair
#define pb push_back
#define fi first
#define se second

using namespace std;

int i,j,m,n,p,k,C[N],a[N],b[N];  //C[i]维护区间和

long long ans;

int lowbit(int x)
{
        return x&-x;  
}

void ins(int x,int y)//修改a[x]的值,a[x]+=y;
{
        for (;x<=n;x+=lowbit(x)) C[x]+=y; //首先需要修改的区间是以x为右端点的,然后下一个区间是x+lowbit(x),以此类推 
}

int ask(int x) //查询[1,x]的值 
{
        int ans=0;
        for (;x;x-=lowbit(x)) ans+=C[x]; //我们询问的]是[x-lowbit(x)+1,x,然后后面一个区间是以x-lowbit(x)为右端点的,依次类推 
        return ans;
}

int main()       
{
        scanf("%d",&n);         //简单的离散化
        for (i=1;i<=n;++i) scanf("%d",&a[i]),b[++b[0]]=a[i];
        sort(b+1,b+b[0]+1); b[0]=unique(b+1,b+b[0]+1)-(b+1);   //unique对于已经排好顺序的数组去重,减去开头位置,得到里面元素的个数
        for (i=1;i<=n;++i) a[i]=lower_bound(b+1,b+b[0]+1,a[i])-b;   
for (i=1;i<=n;++i) ans+=(i-1)-ask(a[i]),ins(a[i],1);
        printf("%lld
",ans);
}

 PS:

 

 

 


 

               

 

 

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<ctime>
#include<set>
#include<vector>
#include<map>
#include<queue>

#define N 300005
#define M 8000005

#define ls (t<<1)
#define rs ((t<<1)|1)
#define mid ((l+r)>>1)

#define mk make_pair
#define pb push_back
#define fi first
#define se second

using namespace std;

int i,j,m,n,p,k,fa[N],deep[N];

int get(int x) //不加任何优化的暴力 
{
        while (fa[x]!=x) x=fa[x];
        return x;
}

//路径压缩
/*
int get(int x) { return fa[x]==x?fa[x]:fa[x]=get(fa[x]); }

//按秩合并 

void Merge(int x,int y)
{
    x=get(x); y=get(y); 使用的是最暴力的get
    if (deep[x]<deep[y]) fa[x]=y;
    else if (deep[x]>deep[y]) fa[y]=x;
    else deep[x]++,fa[y]=x;
}

*/ 

void Merge(int x,int y)
{
        x=get(x); y=get(y); //找到x,y所在连通块的顶点
        fa[x]=y; 
}


int Ask(int x,int y)
{
        x=get(x); y=get(y);
        if (x==y) return 1;//表示连通 
        return 0; 
}

int main()
{
        scanf("%d",&n);
        for (i=1;i<=n;++i) fa[i]=i,deep[i]=1;
        Merge(2,3);//合并2,3所在的连通块
        printf("%d
",Ask(2,3));//询问2,3是否在同一个连通块里 
} 

先拿出相等语句

 

 

      不存在Ai!=Aj   Ai!=Az   Aj!= Az

           


#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<ctime>
#include<set>
#include<vector>
#include<map>
#include<queue>

#define N 300005
#define M 8000005
#define K 18

#define ls (t<<1)
#define rs ((t<<1)|1)
#define mid ((l+r)>>1)

#define mk make_pair
#define pb push_back
#define fi first
#define se second

using namespace std;

int i,j,m,n,p,k,fa[N][K+1],deep[N];

vector<int>v[N];

void dfs(int x) //dfs求出树的形态,然后对fa数组进行处理 
{
        int i;
        for (i=1;i<=K;++i) //fa[x][i]表示的是x向父亲走2^i步走到哪一个节点 
            fa[x][i]=fa[fa[x][i-1]][i-1]; //x走2^i步相当于走2^(i-1)步到一个节点fa[x][i-1],再从fa[x][i-1]走2^(i-1)步 
        for (i=0;i<(int)v[x].size();++i)
        {
                int p=v[x][i];
                if (fa[x][0]==p) continue;
                fa[p][0]=x;
                deep[p]=deep[x]+1; //再记录一下一个点到根的深度deep_x 
                dfs(p);
        }
}

int Kth(int x,int k) //求第k个父亲,利用二进制位来处理 
{
        for (i=K;i>=0;--i) //k可以被拆分成logN个2的整次幂 
            if (k&(1<<i)) x=fa[x][i];
        return x;
}

int Find_LCA(int x,int y) //求x,y的LCA 
{
        int i,k;
        if (deep[x]<deep[y]) swap(x,y);
        x=Kth(x,deep[x]-deep[y]); //把x和y先走到同一深度 
        if (x==y) return x;
        for (i=K;i>=0;--i) //注意到x到根的路径是xa1a2...aic1c2...ck
                           //y到根的路径是        yb1b2...bic1c2...ck 我们要做的就是把x和y分别跳到a_i,b_i的位置,可以发现这段距离是相同的. 
            if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
        return fa[x][0]; 
}

int main()
{
    scanf("%d",&n);
    for (i=1;i<n;++i)
    {
            int x,y;
            scanf("%d%d",&x,&y);
            v[x].pb(y); v[y].pb(x);
    }
    dfs(1);
    printf("%d
",Find_LCA(3,5)); 
}

原文地址:https://www.cnblogs.com/xiaoyezi-wink/p/10792377.html