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。

    重链剖分和长链剖分

    哈哈哈哈哈哈不会。

  • 相关阅读:
    牛客 动物园 (KMP)
    网络流模板与经典模型
    Codeforces Round #698 (Div. 2)
    CF1485X Codeforces Round #701
    CF1479B Painting the Array(贪心+DP)
    「AGC021E」Ball Eat Chameleons
    「AGC034E」 Complete Compress
    「AGC034D」 Manhattan Max Matching
    「ARC103D」 Distance Sums
    「AGC035C」 Skolem XOR Tree
  • 原文地址:https://www.cnblogs.com/jindui/p/11241301.html
Copyright © 2011-2022 走看看