平板电视专项训练 | 附总结

 

 

 

 

 

黑匣子

题目描述

Black Box 是一种原始的数据库。它可以储存一个整数数组,还有一个特别的变量 $i$。最开始的时候 Black Box 是空的.而 $i=0$。这个 Black Box 要处理一串命令。 命令只有两种: - `ADD(x)`:把 $x$ 元素放进 Black Box; - `GET`:$i$ 加 $1$,然后输出 Black Box 中第 $i$ 小的数。 记住:第 $i$ 小的数,就是 Black Box 里的数的按从小到大的顺序排序后的第 $i$ 个元素。 我们来演示一下一个有11个命令的命令串。(如下表所示) | 序号 | 操作 | $i$ | 数据库 | 输出 | | :--: | :--- | :------: | ------ | :----: | | 1 | `ADD(3)` | $0$ | $3$ | / | | 2 | `GET` | $1$ | $3$ | $3$ | | 3 |`ADD(1)`|$1$|$1,3$|/| | 4 |`GET`|$2$|$1,3$|$3$| | 5 |`ADD(-4)`|$2$|$-4,1,3$|/| | 6 |`ADD(2)`|$2$|$-4,1,2,3$|/| | 7 |`ADD(8)`|$2$|$-4,1,2,3,8$|/| | 8 |`ADD(-1000)`|$2$|$-1000,-4,1,2,3,8$|/| | 9 |`GET`|$3$|$-1000,-4,1,2,3,8$|$1$| | 10 |`GET`|$4$|$-1000,-4,1,2,3,8$|$2$| | 11 |`ADD(2)`|$4$|$-1000,-4,1,2,2,3,8$|/| 现在要求找出对于给定的命令串的最好的处理方法。`ADD` 命令共有 $m$ 个,`GET` 命令共有 $n$ 个。现在用两个整数数组来表示命令串: 1. $a_1,a_2,\cdots,a_m$:一串将要被放进 Black Box 的元素。例如上面的例子中 $a=[3,1,-4,2,8,-1000,2]$。 2. $u_1,u_2,\cdots,u_n$:表示第 $u_i$ 个元素被放进了 Black Box 里后就出现一个 `GET` 命令。例如上面的例子中 $u=[1,2,6,6]$ 。输入数据不用判错。

输入输出格式

输入格式

 

第一行两个整数 $m$ 和 $n$,表示元素的个数和 `GET` 命令的个数。 第二行共 $m$ 个整数,从左至右第 $i$ 个整数为 $a_i$,用空格隔开。 第三行共 $n$ 个整数,从左至右第 $i$ 个整数为 $u_i$,用空格隔开。

输出格式

 

输出 Black Box 根据命令串所得出的输出串,一个数字一行。

输入输出样例

输入样例 #1

7 4
3 1 -4 2 8 -1000 2
1 2 6 6

输出样例 #1

3
3
1
2

说明

#### 数据规模与约定 - 对于 $30\%$ 的数据,$1 \leq n,m \leq 10^{4}$。 - 对于 $50\%$ 的数据,$1 \leq n,m \leq 10^{5}$。 - 对于 $100\%$ 的数据,$1 \leq n,m \leq 2 \times 10^{5},|a_i| \leq 2 \times 10^{9}$,保证 $u$ 序列单调不降。

---------------------------

#include<stdio.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
typedef long long ll;
tree<ll,null_type,less<ll>,rb_tree_tag,tree_order_statistics_node_update> bbt;
template<typename T>
inline void read(T &x){
    register bool f=0;register char ch=getchar();x=0;
    for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+(ch&15);
    if(f) x=-x;
}
template<typename T,typename...Args>
inline void read(T &x,Args&...args){read(x);read(args...);}
const int N=2e5+5;
int n,m,k,a[N],u[N];ll ans;
inline void query(){
    ans=*bbt.find_by_order(k);
    ans>>=20;
    printf("%lld\n",ans);
    k++;
}
int main(){
    read(n,m);
    for(int i=1;i<=n;i++) read(a[i]);
    for(int i=1;i<=m;i++) read(u[i]);
    for(int i=1,j=1;i<=n;i++){
        bbt.insert((1LL*a[i]<<20)+i);
        while(u[j]==i){
            query();
            j++;
        }
        if(j>m) break;
    }
    return 0;
}

 

[NOI2004]郁闷的出纳员

题目描述

OIER 公司是一家大型专业化软件公司,有着数以万计的员工。作为一名出纳员,我的任务之一便是统计每位员工的工资。这本来是一份不错的工作,但是令人郁闷的是,我们的老板反复无常,经常调整员工的工资。如果他心情好,就可能把每位员工的工资加上一个相同的量。反之,如果心情不好,就可能把他们的工资扣除一个相同的量。我真不知道除了调工资他还做什么其它事情。 工资的频繁调整很让员工反感,尤其是集体扣除工资的时候,一旦某位员工发现自己的工资已经低于了合同规定的工资下界,他就会立刻气愤地离开公司,并且再也不会回来了。每位员工的工资下界都是统一规定的。每当一个人离开公司,我就要从电脑中把他的工资档案删去,同样,每当公司招聘了一位新员工,我就得为他新建一个工资档案。 老板经常到我这边来询问工资情况,他并不问具体某位员工的工资情况,而是问现在工资第 $k$ 多的员工拿多少工资。每当这时,我就不得不对数万个员工进行一次漫长的排序,然后告诉他答案。 好了,现在你已经对我的工作了解不少了。正如你猜的那样,我想请你编一个工资统计程序。怎么样,不是很困难吧? 如果某个员工的初始工资低于最低工资标准,那么将不计入最后的答案内。

输入输出格式

输入格式

 

第一行有两个非负整数 $n$ 和 $\min$。$n$ 表示下面有多少条命令,$\min$ 表示工资下界。 接下来的 $n$ 行,每行表示一条命令。命令可以是以下四种之一: - `I k` 新建一个工资档案,初始工资为 $k$。如果某员工的初始工资低于工资下界,他将立刻离开公司。 - `A k` 把每位员工的工资加上 $k$ 。 - `S k` 把每位员工的工资扣除 $k$。 - `F k` 查询第 $k$ 多的工资。 在初始时,可以认为公司里一个员工也没有。

输出格式

 

对于每条 `F` 命令,你的程序要输出一行,仅包含一个整数,为当前工资第 $k$ 多的员工所拿的工资数,如果 $k$ 大于目前员工的数目,则输出 $-1$。 输出文件的最后一行包含一个整数,为离开公司的员工的总数。

输入输出样例

输入样例 #1

9 10
I 60
I 70
S 50
F 2
I 30
S 15
A 5
F 1
F 2

输出样例 #1

10
20
-1
2

说明

- `I` 命令的条数不超过 $10^5$; - `A` 和 `S` 命令的总条数不超过 $100$; - `F` 命令的条数不超过 $10^5$; - 每次工资调整的调整量不超过 $10^3$; - 新员工的工资不超过 $10^5$。

 

#pragma GCC optimize(3)
#include<stdio.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
#define mp make_pair
using namespace std;
using namespace __gnu_pbds;
typedef pair<int,int> ll;
tree<ll,null_type,less<ll>,rb_tree_tag,tree_order_statistics_node_update> bbt,tp;
int n,m,ans,dt;char s[2008];
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1,k,sz;i<=n;i++){
        scanf("%s%d",s,&k);
        if(s[0]=='I'){
            if(k>=m){
                bbt.insert(mp(k-dt,i));
            }
        }
        else if(s[0]=='A'){
            dt+=k;
        }
        else if(s[0]=='S'){
            dt-=k;
            bbt.split(mp(m-dt,0),tp);
            swap(bbt,tp);
            ans+=tp.size();
        }
        else if(s[0]=='F'){
            sz=bbt.size();
            printf("%d\n",k>sz?-1:bbt.find_by_order(sz-k)->first+dt);
        }
    }
    printf("%d\n",ans);
    return 0;
}

 

总结

介绍

什么是__gnu_pbds?Policy based data structures!简称平板电视pbds。在使用pbds前,你需要:

#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>//用tree
#include<ext/pb_ds/hash_policy.hpp>//用hash
#include<ext/pb_ds/trie_policy.hpp>//用trie
#include<ext/pb_ds/priority_queue.hpp>//用priority_queue
using namespace __gnu_pbds;

woc,真jb烦,有没有什么简单的方法?当然有:

#include<bits/extc++.h>
using namespace __gnu_pbds;
//bits/extc++.h与bits/stdc++.h类似,bits/extc++.h是所有拓展库,bits/stdc++.h是所有标准库

但是在dev c++里如果这样写,会提示少一个文件,出各种莫名奇妙的锅,其它的IDE请自行尝试,我的linux是deepin的,装了NOI Linux的dalao帮忙测一下。

hash

该引用的头文件和命名空间都讲过了,直接进入正题。

hash_table的用法与map类似,它是这么定义的:

cc_hash_table<int,bool> h;
gp_hash_table<int,bool> h;

其中cc开头为拉链法,gp开头为探测法,个人实测探测法稍微快一些。

啥?操作?其实就和map差不多,支持[ ]和find。

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
gp_hash_table<string,int> h;
void judge(string s)
{
    if(h.find(s)!=h.end())
        cout<<"orz %%%";
    else
        cout<<"tan90";
    cout<<endl;
}
int main()
{
    h["Ican'tAKIOI"]=1;
    h.insert(make_pair("UAKIOI",1));
    string str;
    while(cin>>str)
        judge(str);
    return 0;
}

运行图

等一等?和map一样,那不如直接用map了。不不不,map的总时间复杂度是 $O(nlogn)$ 的,而hash_table的总时间复杂度仅为 $O(n)$ !所以我们可以用这个特性来做洛谷P1333 瑞瑞的木棍。前置知识:并查集 欧拉路

感谢Great_Influence的代码:

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>//pb_ds库
#include<cctype>
#define For(i,a,b) for(i=(a);i<=(b);++i)
#define Forward(i,a,b) for(i=(a);i>=(b);--i)
using namespace std;
using namespace __gnu_pbds;//命名空间
template<typename T>inline void read(T &x){
    T s=0,f=1;char k=getchar();
    while(!isdigit(k)&&k^'-')k=getchar();
    if(!isdigit(k)){f=-1;k=getchar();}
    while(isdigit(k)){s=s*10+(k^48);k=getchar();}
    x=s*f;
}
void file(void){
    #ifndef ONLINE_JUDGE
    freopen("P1333.in","r",stdin);
    freopen("P1333.out","w",stdout);
    #endif
}
const int MAXN=250050;
char l[15],r[15];
int e,f[MAXN];
bool in[MAXN];
gp_hash_table<string,int>G;//hash_table定义
int find(int x)
{
    int t=f[x],d;while(t!=f[t])t=f[t];
    while(f[x]!=t){d=f[x];f[x]=t;x=d;}
    return t;
}
void work()
{
    while(scanf("%s%s",l,r)!=EOF)
    {
        if(!G[l])G[l]=++e,f[e]=e;
        if(!G[r])G[r]=++e,f[e]=e;
        if(e>250010)//如果点数超过n+1的话,一定不存在通路,直接返回。
        {
            printf("Impossible\n");
            return;
        }
        in[G[l]]^=1;//修改奇偶情况
        in[G[r]]^=1;
        f[find(G[l])]=find(G[r]);//合并并查集
    }
    int flag=0;
    int i;
    For(i,1,e)if(in[i])//判断奇点个数是否超过2
    {
        if(flag==2)
        {
            printf("Impossible\n");
            return;
        }
        else ++flag;
    }
    int father=find(1);
    For(i,2,e)if(find(i)^father)//判断是否连通
    {
        printf("Impossible\n");
        return;
    }
    printf("Possible\n");
}
int main(void){
    file();
    work();
    return 0;
}

tree

pbds里面的tree都是平衡树,其中有rb_tree,splay_tree,ov_tree(后两种都容易超时,所以请不要用它们)。需要的头文件与命名空间也讲了,下面我们来看它的食用方法:

#define pii pair<int,int>
#define mp(x,y) make_pair(x,y) 
tree<pii,null_type,less<pii>,rb_tree_tag,tree_order_statistics_node_update> tr;
pii //存储的类型
null_type //无映射(低版本g++为null_mapped_type)
less<pii> //从小到大排序
rb_tree_tag //红黑树
tree_order_statistics_node_update //更新方式 
tr.insert(mp(x,y)); //插入;
tr.erase(mp(x,y)); //删除;
tr.order_of_key(pii(x,y)); //求排名 
tr.find_by_order(x); //找k小值,返回迭代器 
tr.join(b); //将b并入tr,前提是两棵树类型一样且没有重复元素 
tr.split(v,b); //分裂,key小于等于v的元素属于tr,其余的属于b
tr.lower_bound(x); //返回第一个大于等于x的元素的迭代器
tr.upper_bound(x); //返回第一个大于x的元素的迭代器
//以上所有操作的时间复杂度均为O(logn) 

下面我们来试一试洛谷P3369 普通平衡树(感谢shenben的代码):

//by shenben
#include<cstdio>
#include<iostream>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
typedef long long ll;
tree<ll,null_type,less<ll>,rb_tree_tag,tree_order_statistics_node_update> bbt;
int n;ll k,ans;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
} 
int main(){
    n=read();
    for(int i=1,opt;i<=n;i++){
        opt=read();k=read();
        if(opt==1) bbt.insert((k<<20)+i);
        if(opt==2) bbt.erase(bbt.lower_bound(k<<20));
        if(opt==3) printf("%d\n",bbt.order_of_key(k<<20)+1);
        if(opt==4) ans=*bbt.find_by_order(k-1),printf("%lld\n",ans>>20);
        if(opt==5) ans=*--bbt.lower_bound(k<<20),printf("%lld\n",ans>>20);
        if(opt==6) ans=*bbt.upper_bound((k<<20)+n),printf("%lld\n",ans>>20);
    }
    return 0;
}

前方高能!前方高能!前方高能!

在看这里之前,你需要熟练地掌握c++的特性。如果看不懂我也没有办法,你可以跳过这一部分。

你以为pbds种的tree只能实现这些功能?不不不,你可以自定义它,我们需要写一个自己的node_update,它是长这样的:

template<class Node_CItr,class Node_Itr,class Cmp_Fn,class _Alloc>
struct my_node_update
{
    typedef my_type metadata_type;
    void operator()(Node_Itr it, Node_CItr end_it)
    {
        ...
    }
};

我们先解释一下这个类是如何工作的。节点更新的tree都会保存一个my_type类型的变量。当我们修改这棵树的时候,会从叶子节点开始修改,并且每次都会调用operator(),我们来看一下这个函数的两个参数:

Node_Itr it为调用该函数的元素的迭代器,Node_CItr end_it可以const到叶子节点的迭代器,Node_Itr有以下的操作:

1.get_l_child(),返回其左孩子的迭代器,没有则返回node_end;

2.get_r_child(),同get_l_child();

3.get_metadata(),返回其在树中维护的数据;

4.**it可以获取it的信息。

为了详细讲解,我们举一个更新子树大小的例子:

void operator()(Node_Itr it, Node_CItr end_it)
{
    Node_Itr l=it.get_l_child();
    Node_Itr r=it.get_r_child();
    int left=0,right=0;
    if(l!=end_it) left=l.get_metadata();
    if(r!=end_it) right=r.get_metadata();
    const_cast<int&>(it.get_metadata())=left+right+1;
}

现在我们学会了更新,那么我们该如何自己写操作呢?node_update所有public方法都会在树中公开。如果我们在node_update中将它们声明为virtual,则可以访问基类中的所有virtual。所以,我们在类里添加以下内容:

virtual Node_CItr node_begin() const=0;
virtual Node_CItr node_end() const=0;

这样我们就能直接访问树了,还有,node_begin指向树根,node_end指向最后一个叶子节点的后一个地址,下面这个就是查排名的操作:

int myrank(int x)
{
    int ans=0;
    Node_CItr it=node_begin();
    while(it!=node_end())
    {
        Node_CItr l=it.get_l_child();
        Node_CItr r=it.get_r_child();
        if(Cmp_Fn()(x,**it))
            it=l;
        else
        {
            ans++;
            if(l!=node_end()) ans+=l.get_metadata();
            it=r;
        }
    }
    return ans;
}

下面我们来看CF459D

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
template<class Node_CItr,class Node_Itr,class Cmp_Fn,class _Alloc>
struct my_node_update
{
    typedef int metadata_type;
    int order_of_key(pair<int,int> x)
    {
        int ans=0;
        Node_CItr it=node_begin();
        while(it!=node_end())
        {
            Node_CItr l=it.get_l_child();
            Node_CItr r=it.get_r_child();
            if(Cmp_Fn()(x,**it))
                it=l;
            else
            {
                ans++;
                if(l!=node_end()) ans+=l.get_metadata();
                it=r;
            }
        }
        return ans;
    }
    void operator()(Node_Itr it, Node_CItr end_it)
    {
        Node_Itr l=it.get_l_child();
        Node_Itr r=it.get_r_child();
        int left=0,right=0;
        if(l!=end_it) left =l.get_metadata();
        if(r!=end_it) right=r.get_metadata();
        const_cast<int&>(it.get_metadata())=left+right+1;
    }
    virtual Node_CItr node_begin() const = 0;
    virtual Node_CItr node_end() const = 0;
};
tree<pair<int,int>,null_type,less<pair<int,int> >,rb_tree_tag,my_node_update> me;
int main()
{
    map<int,int> cnt[2];
    int n;
    cin>>n;
    vector<int> a(n);
    for(int i=0;i<n;i++)
        cin>>a[i];
    vector<int> pre(n),suf(n);
    for(int i=0;i<n;i++)
    {
        pre[i]=cnt[0][a[i]]++;
        suf[n-i-1]=cnt[1][a[n-i-1]]++;
    }
    long long ans=0;
    for(int i=1;i<n;i++)
    {
        me.insert({pre[i-1],i-1});
        ans+=i-me.order_of_key({suf[i],i});
    }
    cout<<ans<<endl;
}

trie

trie即为字典树,我们先看如何定义一个trie与它的操作:

typedef trie<string,null_type,trie_string_access_traits<>,pat_trie_tag,trie_prefix_search_node_update> tr;
//第一个参数必须为字符串类型,tag也有别的tag,但pat最快,与tree相同,node_update支持自定义
tr.insert(s); //插入s 
tr.erase(s); //删除s 
tr.join(b); //将b并入tr 
pair//pair的使用如下:
pair<tr::iterator,tr::iterator> range=base.prefix_range(x);
for(tr::iterator it=range.first;it!=range.second;it++)
    cout<<*it<<' '<<endl;
//pair中第一个是起始迭代器,第二个是终止迭代器,遍历过去就可以找到所有字符串了。 

现在我们来看Astronomical Database

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/trie_policy.hpp>
using namespace std;
using namespace __gnu_pbds; 
typedef trie<string,null_type,trie_string_access_traits<>,pat_trie_tag,trie_prefix_search_node_update>pref_trie;
int main()
{
    pref_trie base;
    base.insert("sun");
    string x;
    while(cin>>x)
    {
        if(x[0]=='?')
        {
            cout<<x.substr(1)<<endl;
            auto range=base.prefix_range(x.substr(1));
            int t=0;
            for(auto it=range.first;t<20 && it!=range.second;it++,t++)
                cout<<"  "<<*it<<endl;
        }
        else
            base.insert(x.substr(1));
    }
}

priority_queue

priority_queue为优先队列,用堆实现,priority_queue的定义与操作:

priority_queue<int,greater<int>,TAG> Q;//小根堆,大根堆写less<int>
/*其中的TAG为类型,有以下几种:
pairing_heap_tag
thin_heap_tag
binomial_heap_tag
rc_binomial_heap_tag 
binary_heap_tag
其中pairing_help_tag最快*/
Q.push(x);
Q.pop();
Q.top();
Q.join(b);
Q.empty();
Q.size(); 
Q.modify(it,6);
Q.erase(it);
//以上操作我都不讲了,pbds里的优先队列还可以用迭代器遍历

时间复杂度:

时间复杂度

堆优化dijkstra(感谢Great_Influence的代码):

#include<bits/stdc++.h>
#include<ext/pb_ds/priority_queue.hpp>
#define Rep(i,a,b) for(register int i=(a),i##end=(b);i<=i##end;++i)
#define Repe(i,a,b) for(register int i=(a),i##end=(b);i>=i##end;--i)
#define For(i,a,b) for(i=(a),i<=(b);++i)
#define Forward(i,a,b) for(i=(a),i>=(b);--i)
#define Chkmax(a,b) a=a>b?a:b
template<typename T>inline void read(T &x)
{
    T f=1;x=0;char c;
    for(c=getchar();!isdigit(c);c=getchar())if(c=='-')f=-1;
    for(;isdigit(c);c=getchar())x=x*10+(c^48);
    x*=f;
}

inline void write(int x)
{
    if(!x){putchar(48);putchar('\n');return;}
    static int sta[45],tp;
    for(tp=0;x;x/=10)sta[++tp]=x%10;
    for(;tp;putchar(sta[tp--]^48));
    putchar('\n');
}

using namespace std;
void file()
{
#ifndef ONLINE_JUDGE
    freopen("water.in","r",stdin);
    freopen("water.out","w",stdout);
#endif
}

const int MAXN=1e5+7,MAXM=4e5+7;

static int n,m;

static struct edg
{
    int u,v,w,h;
    friend bool operator<(edg a,edg b){return a.h>b.h;}
}EDG[MAXM];

static struct edge
{
    int v,w,nxt;
}P[MAXM<<1];

static int head[MAXN],e;

inline void add(int u,int v,int w)
{P[++e]=(edge){v,w,head[u]};head[u]=e;}

__gnu_pbds::priority_queue<pair<int,int>,greater<pair<int,int> > >G;

__gnu_pbds::priority_queue<pair<int,int>,greater<pair<int,int> > >::point_iterator its[MAXN];

static int dis[MAXN];

const int INF=2e9+7;

inline void dijkst(int s)
{
    G.clear();
    its[s]=G.push(make_pair(0,s));dis[s]=0;
    Rep(i,2,n)dis[i]=INF,its[i]=G.push(make_pair(INF,i));
    static int u;
    while(!G.empty())
    {
        u=G.top().second;G.pop();
        for(register int v=head[u];v;v=P[v].nxt)
            if(dis[P[v].v]>dis[u]+P[v].w)
            {
                dis[P[v].v]=dis[u]+P[v].w;
                G.modify(its[P[v].v],make_pair(dis[u]+P[v].w,P[v].v));
            }
    }
}

static int s;

inline void init()
{
    read(n);read(m);read(s);
    static int u,v,w;
    Rep(i,1,m)read(u),read(v),read(w),add(u,v,w);
}

inline void solve()
{
    dijkst(s);
    Rep(i,1,n)printf("%d ",dis[i]);
    puts("");

}

int main()
{
    file();
    init();
    solve();
    return 0;
}

关于rope

sorry,rope属于__gnu_cxx,不属于__gnu_pbds。下次讲ext中其他的内容时,我会讲rope。

原文https://www.luogu.com.cn/blog/Chanis/gnu-pbds 

posted @ 2020-04-24 20:06  神犇(shenben)  阅读(416)  评论(0编辑  收藏  举报