zoukankan      html  css  js  c++  java
  • 2019 CCF夏令营 day 2

    堆,并查集,加权并查集,树链剖分(重链,长链),lca。

    单次严格o(lgn)插入、删除最小(最大)的数字,o(1)询问最小(最大)的数字,实践中一个点常用x*2,x*2+1,x/2代表其左右儿子和父亲。

    删除任意数字

    除了插入/删除堆顶,还要实现删除堆中任意数字(要保证其一定在堆中)

    用一个堆来维护需要删除的数字,每次取堆顶时看看这个元素是否被删除了即可。

    堆排序   o(nlgn)

    带插入的中位数

    每次插入一个数字,然后询问所有数字的中位数

    维护一个大根堆和一个小根堆,维护两堆之间数量之差不超过1,o(nlgn)

    行有序数表第k大

    有一个n*m的数表,每一行数字从小到大。询问这个数表中第k小的数字,要求(忽略读入复杂度)复杂度O((n+k)lgn)。

    每次从某行中取一个目前的最小值,用一个堆维护每行的开头,每次取出当前最小值删掉,若这一行还有数字就再加进去。

    第k小点对和

    给你两个序列a,b,求a(x)+b(y)的第k小。要求O((n+k)lgn)

    与上一个相似,a(x)作为一行数表,b(y)作为另一行数表。

    t1 合并石子

    洛谷1090

    #include <bits/stdc++.h>
    using namespace std;
    int n,u,ans;
    priority_queue< int ,vector<int>,greater<int> >q;
    int main(){
        cin>>n;
        for(int i=1;i<=n;i++)
        {    cin>>u;
            q.push(u);
        }
        while(q.size()>=2)
        {    int x=q.top();
            q.pop();
            int y=q.top();
            q.pop();
            ans+=x+y;
            q.push(x+y);
        }
        cout<<ans;
        return 0;
    }
    View Code

    t2

    一条街上有n个白点,坐标依次是x_1~x_n; 有个人一开始在0。选择第i个点涂黑要付出a_i的代价(必须走到这个点)。最后必须回到0。 选出某k个点涂黑的代价是这k个点的a的和,加上两倍的坐标最大值。 对每个k=1…n求,涂黑恰好k个不同的点的最大代价。 n<=100000

    (此题洛谷2672推销员,先默默骂一下,题目给改的乱七八糟)

    先搞出1的情况,可以见得2的情况为,(1.)2在1左面,则为s1+a1+a2,(2.)在1右面,则为s2+a1+a2

    s2>s1;

    所以最好选右面的;用两个堆,一个堆放s*2+a,一个放a,对应两种情况,维护一个堆序号在当前最远右面,一个在左面。

    #include <iostream>
    #include <cstdio>
    #include <queue>
    #include <algorithm>
    using namespace std;
    
    priority_queue < pair<int,int> > q1;
    priority_queue <int> q2;
    struct llo{
        int s,a;
    } e[100002];
    int now,n,q11,q22,ans;
    
    bool cmp(llo x,llo y){return x.s<y.s;}
    int main(){
        scanf("%d",&n);
        for(int i=1;i<=n;i++)    scanf("%d",&e[i].s);
        for(int i=1;i<=n;i++)    scanf("%d",&e[i].a);
        sort(e+1,e+n+1,cmp);
        for(int i=1;i<=n;i++)    q1.push(make_pair(e[i].s*2+e[i].a,i));
        now=0;
        for(int i=1;i<=n;i++){
            q11=q22=0;
            if(!q2.empty())    q22=q2.top();
            while(!q1.empty()&&q1.top().second<=now)    q1.pop();
            if(!q1.empty())    q11=q1.top().first;
            if(q22<=q11-e[now].s*2){
                ans+=q11-e[now].s*2;
                for(int j=now+1;j<q1.top().second;j++)
                    q2.push(e[j].a);
                now=q1.top().second;
                q1.pop();
            
            }
            else{
                ans+=q22;
                q2.pop();
            }
            printf("%d
    ",ans);
        }
        return 0;
    }
    View Code

    并查集

    启发式合并

    对每个集合维护一个大小,每次把小的集合的代表元素的父亲设为大的集合元素的代表元素,这样每跳一步,其子树大小就会至少翻倍,每个点任意时刻到根的路径的长度都是o(lgn)的。

    把小的弄到大的上去的操作就叫做启发式合并。

    按秩合并

    每个集合维护深度最大,每次把深度小的挂到深度大的上去。

    显然新的集合深度变大1当且仅当原先两个集合深度相等,因此可以归纳证明每次最大深度+1,集合大小也会翻倍,复杂度和上一个一样

    路径压缩

    太简单了不说了

    复杂度是基于均摊的(就是尽管某些单步代价很高但是总代价不大),我不会证明,据说是O(n+log_{1+m/n} (n或者m))的,总之也是O(nlgn)级别的。

    Tarjan很牛的证明了当路径压缩和按秩合并一块用的时候复杂度是O(nalpha(n))的,其中alpha(n)是阿克曼的某个反函数,增长慢到大概你取n是全宇宙的原子总数,这玩意也不超过6大概。

    加权并查集

    本质上就是并查集中每个点到父亲的边有一个权值,表示原图中这个点到父节点的某些信息。

    或者这个点到父节点的某些信息。

    kruskal算法

    o(mlgn)它有个十分重要的性质叫环切性质,对于一条没有被选中的边(称非树边),会和选中的边(树边)形成恰好一个简单环,那么这条非树边的权值一定比所有树边的权值大

    借由这个性质,假设kruskal求出的树不是最优的最小生成树,那么考虑第一条加错的边,其在最优的最小生成树中对应的环,一定存在一条边权值比它大,这样就不满足环切性质了,也就是一定可以做一个替换使得这个最小生成树更小,矛盾。 因此kruskal求出的是最小生成树。 顺带一提,若要求两个点的一条路径使得边权最大值最小,那么使用最小生成树上的路径一定最优。 因为本质上就是从小到大加边到第一次二者连通,就是kruskal求出的东西。也可以用环切性质说明。

    星球大战(早忘了几百年了)

    #include <iostream>
    #include <cstdio>
    #include <vector>
    #define MA 400002
    using namespace std;
    
    int n,m,a[MA],b[MA],f[MA],t,star[MA],broken[MA],iis[MA],tot;
    vector <int> son[400002];
    
    int find(int x){
        if(f[x]!=x)    f[x]=find(f[x]);
        return f[x];
    }
    
    int main(){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++){
            scanf("%d%d",&a[i],&b[i]);
            son[a[i]].push_back(b[i]);
            son[b[i]].push_back(a[i]);
        }
        for(int i=0;i<n;i++)    f[i]=i;
        scanf("%d",&t);
        for(int i=1;i<=t;i++){
            scanf("%d",&star[i]);
            broken[star[i]]=-1;
        }
        tot=n-t;
        for(int i=1;i<=m;i++){
            int x=a[i],y=b[i];
            if(broken[x]==-1||broken[y]==-1)    continue;
            else{
                int fx=find(x);
                int fy=find(y);
                if(fx!=fy){
                    f[fx]=fy;
                    tot--;
                }    
            }
        }
        iis[t+1]=tot;
        for(int i=t;i>=1;i--){
            //printf("%d ",broken[2]);
            tot++;
            //cout<<endl;
            broken[star[i]]=0;
            for(int j=0;j<son[star[i]].size();j++){
                int x=star[i],y=son[star[i]][j];
                if(broken[y]==-1)    continue;
                //printf("%d %d
    ",x,y);
                int fx=find(x);
                int fy=find(y);
                if(fx!=fy){
                    f[fy]=fx;
                    tot--;
                }    
            }
            iis[i]=tot;
        }
        for(int i=1;i<=t+1;i++)
            printf("%d
    ",iis[i]);
        return 0;
    }
    View Code

    关押罪犯

    #include <bits/stdc++.h>
    using namespace std;
    
    struct ll{
        int mm,enemy,w;
    } en[2000008];
    int n,m,f[200009];
    
    bool cmp(ll x,ll y){
        return x.w>y.w;
    }
    
    int find(int x){
        if(f[x]!=x)    f[x]=find(f[x]);
        return f[x];
    }
    
    int main(){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=2*n;i++)
            f[i]=i;
        for(int i=1;i<=m;i++){
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            en[i].enemy=b;
            en[i].mm=a;
            en[i].w=c;
        } 
        int ff=0;
        sort(en+1,en+m+1,cmp);
        for(int i=1;i<=m;i++){
            int x=en[i].mm,y=en[i].enemy;
            if(find(x)==find(y)||find(x+n)==find(y+n)){
                ff=en[i].w;
                break;
            }
            f[f[x]]=f[y+n];
            f[f[x+n]]=f[y];
        }
        printf("%d",ff);
        return 0;
    }
    View Code

    存储:vector,链式前向星

    倍增求LCA

    一个跟链有关的结论:

    有一个在某些题中很重要的性质是,两条链的交集还是一条链,并且新链的端点的LCA是原先某条链的端点的LCA。

    重链剖分和长链剖分

    哈哈哈哈哈哈不会。

  • 相关阅读:
    【故障处理】ORA-12162: TNS:net service name is incorrectly specified (转)
    android studio 编程中用到的快捷键
    java时间格式串
    android Error occurred during initialization of VM Could not reserve enough space for object heap Could not create the Java virtual machine.
    linux安装vmware
    x1c 2017 安装mint18的坑——grub2
    x1c2017 8G版 win linux的取舍纠结记录
    python的try finally (还真不简单)
    kafka+docker+python
    json文件不能有注释
  • 原文地址:https://www.cnblogs.com/jindui/p/11241301.html
Copyright © 2011-2022 走看看