NOIP专题复习——专题一:数据结构基础

模块一:堆

模板

题目来源:【洛谷 3378】堆

首先是STL的堆(优先队列),性能稳定,功能强大,常数略大。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
priority_queue <int, vector<int>, greater<int> > q1;//小根堆
//priority_queue <int> q1; 默认是大根堆,这样定义。
int main(){
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++){
        int a, b;
        scanf("%d", &a);
        if(a == 1) scanf("%d", &b);
        if(a == 1) q1.push(b);
        else if(a == 2) printf("%d\n", q1.top());
        else if(a == 3) q1.pop();
    }
    return 0;
}

手写的堆,功能比较单一,函数需要自己写,根据题目特性写函数,效率高,常数小。

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
struct heap{
    int a[1000010], p;
    heap(){memset(a, 0, sizeof a);p = 0;}
    void up(int x){ //上浮
        if(x>>1 == 0) return;
        if(a[x] < a[x>>1]) swap(a[x], a[x>>1]), up(x>>1);
    }
    void down(int x){ //下沉
        if((x<<1) > p) return;
        if((x<<1)+1 <= p && a[x] > min(a[x<<1], a[(x<<1)+1])){
            if(a[x<<1] < a[(x<<1)+1]){
                swap(a[x<<1], a[x]), down(x<<1);
            }else swap(a[(x<<1)+1], a[x]), down((x<<1)+1);
        }else if(a[x] > a[x<<1]) swap(a[x], a[x<<1]);
    }
    inline void push(int val){a[++p] = val, up(p);}
    inline void pop(){swap(a[1], a[p]), p --, down(1);}
    inline int top(){return a[1];}
    bool empty(){return p == 0;}
};
heap q1;
int main(){
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++){
        int a, b;
        scanf("%d", &a);
        if(a == 1) scanf("%d", &b);
        if(a == 1) q1.push(b);
        else if(a == 2) printf("%d\n", q1.top());
        else if(a == 3) q1.pop();
    }
    return 0;
}

优化Dijstra算法

题目来源:【洛谷 1951】收费站

看题目就是一道二分+最短路的题,这里用来练习一下Dijstra算法。

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <iostream>
#include <queue>
#define pii pair<ll,int>
#define mp(a,b) make_pair(a,b)
#define fi first
#define se second
using namespace std;
typedef long long ll;
const ll inf = 1e16;
const int maxn = 10010;
const int maxm = 100010;
int _first[maxn], _to[maxm], _next[maxm], _val[maxn], cnt, n, m, s, t, v, ans;
ll _dis[maxm], dis[maxn];
bool vis[maxn];
priority_queue <pii, vector<pii>, greater<pii> > q1;
void add(int a, int b, ll c){
    _next[++cnt] = _first[a];
    _first[a] = cnt;
    _to[cnt] = b;
    _dis[cnt] = c;
}
ll dij(int S, int T, int x){
    if(_val[S] > x) return inf;
    while(!q1.empty()) q1.pop();
    for(int i = 1; i <= n; i ++) dis[i] = inf, vis[i] = 0;
    dis[S] = 0;
    q1.push(mp(dis[S], S));
    while(!q1.empty()){
        pii t = q1.top();
        q1.pop();
        if(vis[t.se]) continue;
        vis[t.se] = 1;
        if(vis[T]) return dis[T];

        for(int i = _first[t.se]; i; i = _next[i]){
            if(_val[_to[i]] <= x && !vis[_to[i]] && dis[_to[i]] > _dis[i] + dis[t.se]){
                dis[_to[i]] = _dis[i] + dis[t.se];
                q1.push(mp(dis[_to[i]], _to[i]));
            }
        }
    }
    return dis[T];
}
int main(){
    scanf("%d%d%d%d%d", &n, &m, &s, &t, &v);
    for(int i = 1; i <= n; i ++) scanf("%d", &_val[i]);
    for(int i = 1; i <= m; i ++){
        int a, b; ll c;
        scanf("%d%d%lld", &a, &b, &c);
        add(a,b,c), add(b,a,c);
    }
    int l = 0, r = 1e9, ans = -1;
    while(l <= r){
        int mid = (l+r) >> 1;
        ll dd = dij(s, t, mid);
        if(dd > v) l = mid + 1;
        else r = mid - 1, ans = mid;
    }
    printf("%d", ans);
    return 0;
}

堆的应用

题1 题目来源:【洛谷 2085】最小函数值

首先这些函数在定义域内都是单调递增的,所以我们可以把1这个位置的函数值压入堆中,每次取出一个最小的,再把定义域中下一个数的值压入堆中。

#include <iostream>
#include <queue>
#include <cstdio>
using namespace std;
#define maxn 10010;
class f1{
public:
    int a1, b1, c1;
    int now, val;
    bool operator < (const f1 &b)const{
        return val > b.val;
    }
};
priority_queue <f1> q1;
int main(){
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i ++){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        f1 t;
        t = (f1){a,b,c,1,a+b+c};
        q1.push(t);
    }
    while(m --){
        f1 t = q1.top();
        q1.pop();
        printf("%d ", t.val);
        t.now ++;
        t.val = t.a1*t.now*t.now+t.b1*t.now+t.c1;
        q1.push(t);
    }
    return 0;
}
题2 题目来源:【洛谷 3045】牛券

贪心的选择买牛的方案。如果现在还没用完 k 张券,钱就用没了,那肯定是要买金钱前k小的。如果卖完用券之后金钱前 k 小的之后还有剩余的钱,那么再买前k小的就不一定最优。设一个用券后前 k 小牛A的原价是 P1 ,折扣以后的价格是 C1 ,设一个不是前 k 小牛B的原价是 P2 ,折扣以后的价格是 C2 ,假如我们还剩下 R 的钱,我们考虑用B牛替换目前暂时选中的 A 牛,就是说让A牛原价购买,我们多加入一头牛额外花费的代价是 P1C1+P2 ,我们希望新插入一个的代价尽量小,也就是让 P1C1+P2 尽可能的小。因为 A,B 两牛之间没有关系,所以我们可以分别选剩下的里面用券之后尽量小的,已经用券的里面选择原价-优惠价最小的,花费 P1C1+P2 的代价新选入一头牛,或者是选择原价 C2
我的代码找不到了。。。

//by hzwer
#include<cmath>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define pa pair<int,int>
#define inf 1000000000
#define eps 1e-8
#define ll long long
using namespace std;
ll read()
{
    ll 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;
}
priority_queue<int,vector<int>,greater<int> >q;
int n,K,ans;
ll m,sum;
struct data{
    int p,c;
}a[50005];
bool cmpc(data a,data b)
{
    return a.c<b.c;
}
bool cmpp(data a,data b)
{
    return a.p<b.p;
}
int main()
{
    n=read();K=read();m=read();
    for(int i=1;i<=n;i++)
        a[i].p=read(),a[i].c=read();
    sort(a+1,a+n+1,cmpc);
    for(int i=1;i<=K;i++)
    {
        sum+=a[i].c;
        q.push(a[i].p-a[i].c);
        if(sum>m){printf("%d\n",i-1);return 0;}
        if(i==n){printf("%d\n",n);return 0;}
    }
    sort(a+K+1,a+n+1,cmpp);
    ans=K;
    for(int i=K+1;i<=n;i++)
    {
        int t=inf;
        if(!q.empty())t=q.top();
        if(t<a[i].p-a[i].c)
        {
            sum+=t;q.pop();
            q.push(a[i].p-a[i].c);
            sum+=a[i].c;
        }
        else sum+=a[i].p;
        if(sum>m)break;
        else ans++;
    }
    printf("%d\n",ans);
    return 0;
}

模块二:并查集

并查集可以实现最小生成树,还有重要的一部分用并查集做的题目,一些题目用加权并查集真的非常好做。

题1 题目来源:【洛谷 2814】家谱

用并查集维护家族之间的信息,用map离散化。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <map>
#include <string>
using namespace std;
map <string, int> map1;
int f_int, now;
string p[50010], t, f_string;
char c;
int fa[50010], cnt;
int find(int x){return fa[x] == x ? x : fa[x] = find(fa[x]);}
int main(){
    for(int i = 1; i <= 50000; i ++) fa[i] = i;
    c = getchar();
    while(c != '$'){
        cin >> t;
        int now = map1[t];
        if(now == 0) now = map1[t] = ++cnt, p[cnt] = t;
        if(c == '#'){f_int = now;}
        if(c == '+'){if(find(now) != find(f_int)) fa[fa[now]] = fa[f_int];}
        if(c == '?'){cout << t << " " << p[find(now)] << endl;}
        do c = getchar(); while(c!='#'&&c!='+'&&c!='?'&&c!='$');
    }
    return 0;
} 
题2 题目来源:【洛谷 1955】程序自动分析

还是要用到并查集来维护信息,需要用map离散化。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
using namespace std;
int T, n, fa[200010];
struct node{
    int a, b, c;
    bool operator < (const node &t)const{return c > t.c;}
};
map <int,int> map1;
node p[100010];
int j, ok, cnt;
inline int find(int x){return fa[x] == x ? x : fa[x] = find(fa[x]);}
int main(){
    scanf("%d", &T);
    while(T --){
        scanf("%d", &n);
        map1.clear(), ok = 0, cnt = 0;
        for(int i = 1; i <= n; i ++){
            int a, b, c, t1, t2;
            scanf("%d%d%d", &a, &b, &c);
            t1 = map1[a];
            if(t1 == 0) t1 = map1[a] = ++cnt;
            t2 = map1[b];
            if(t2 == 0) t2 = map1[b] = ++cnt;
            p[i].a = t1;
            p[i].b = t2;
            p[i].c = c;
        }
        for(int i = 1; i <= cnt; i ++) fa[i] = i;
        sort(p+1, p+1+n);
        for(j = 1; p[j].c == 1; j ++){
            if(find(p[j].a)!=find(p[j].b)) fa[fa[p[j].a]] = fa[p[j].b];
        }
        for( ; j <= n; j ++){
            if(find(p[j].a) == find(p[j].b)){
                printf("NO\n"), ok = 1;
                break;
            }
        }
        if(!ok) printf("YES\n");
    }
    map1.clear();
    return 0;
}
题3 题目来源:【洛谷 1525】关押罪犯

我的思路是用加权并查集,如果每次两个人不能在一个监狱里权值设置为1,同一个监狱的权值为0,路径压缩的时候对权值进行 mod2 的操作。
把仇恨值按照降序排序,如果两个人不在同一个集合中,加入到一个集合里;如果在一个集合里,那这个仇恨值就是答案了。贪心性质很明显,不证了。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
struct node{
    int a, b, val;
    bool operator < (const node &t)const{
        return val > t.val;
    } 
};
node p[100010];
int fa[20010], dis[20010], n, m;
inline int find(int x){
    if(fa[x] == x) return x;
    int t = fa[x];
    fa[x] = find(fa[x]);
    dis[x] = (dis[t] + dis[x]) % 2;
    return fa[x];
}
int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i ++) fa[i] = i;
    for(int i = 1; i <= m; i ++) scanf("%d%d%d", &p[i].a, &p[i].b, &p[i].val);
    sort(p+1, p+1+m);
    for(int i = 1; i <= m; i ++){
        if(find(p[i].a) != find(p[i].b)){
            dis[fa[p[i].a]] = (dis[p[i].a]^dis[p[i].b]) ? 0 : 1;
            fa[fa[p[i].a]] = fa[p[i].b];
        }else if(!(dis[p[i].a]^dis[p[i].b])){
            printf("%d", p[i].val);
            return 0;
        }
    }
    printf("0");
    return 0;
}
题4 题目来源:【POJ 1611】The Suspects

题意:
有很多组学生,在同一个组的学生经常会接触,也会有新的同学的加入。但是SARS是很容易传染的,只要在改组有一位同学感染SARS,那么该组的所有同学都被认为得了SARS。现在的任务是计算出有多少位学生感染SARS了。假定编号为0的同学是得了SARS的。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n, m, t, p;
int num[30010], fa[30010];
int find(int x){return fa[x]==x ? x : fa[x]=find(fa[x]);}
int main(){
    while(scanf("%d%d", &n, &m) != EOF && n + m){
        for(int i = 0; i <= n; i ++) fa[i] = i, num[i] = 1;
        for(int i = 1; i <= m; i ++){
            scanf("%d%d", &t, &p);
            for(int j = 2, a; j <= t; j ++){
                scanf("%d", &a);
                if(find(a)!=find(p)){
                    num[fa[p]] += num[fa[a]];
                    fa[fa[a]] = fa[p];
                }
            }
        }
        printf("%d\n", num[fa[find(0)]]);
    }
    return 0;
} 
更多练习题目:点击传送

模块三:双向链表

模板题 题目来源:【洛谷 1160】 队列安排
思路:

结构体+指针模拟双向链表,用内存池分配内存空间,设置两个端点指针,表示头和尾。
剩下的就是乱搞了,怎么说这也是初赛的内容。

代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
struct node{
    int num;
    node *pre, *nxt;
};
node lis[100010];
node *pos[100010];
int a1[100010];
int n, m, cnt;
node *head, *p1, *p2, *t;

node* get_newone(int x){
    node *newone = &lis[++cnt];
    newone->num = x;
    return newone;
}
void del(int x){
    node *now = pos[x];
    now->nxt->pre = now->pre;
    now->pre->nxt = now->nxt;
    return;
}
void print(){
    for(node *i = p1->nxt; i != p2; i = i->nxt)
        printf("%d ", i->num);
}
int main(){
    scanf("%d", &n);
    p1 = get_newone(0), p2 = get_newone(n+1);
    t = get_newone(1);
    p1->pre = NULL, p2->nxt = NULL;
    p1->nxt = t, p2->pre = t;
    t->nxt = p2, t->pre = p1;
    pos[1] = t;
    for(int i = 2; i <= n; i ++){
        int a, b;
        scanf("%d%d", &a, &b);
        if(b == 0){ // left
            node *now = pos[a];
            node *newone = get_newone(i);
            newone->nxt = now;
            newone->pre = now->pre;
            now->pre->nxt = newone;
            now->pre = newone;
            pos[i] = newone;
        }else{ // right
            node *now = pos[a];
            node *newone = get_newone(i);
            newone->nxt = now->nxt;
            newone->pre = now;
            now->nxt->pre = newone;
            now->nxt = newone;
            pos[i] = newone;
        }
    }

    scanf("%d", &m);
    for(int i = 1; i <= m; i ++) scanf("%d", &a1[i]);
    sort(a1+1, a1+m+1), m = unique(a1+1, a1+m+1) - a1-1;
    for(int i = 1; i <= m; i ++) del(a1[i]);
    print();
    return 0;
}

模块四:ST表

ST表主要应用于一些查询区间最值的题目,对于一段不变的区间,ST表可以做到 O(nlogn)O(1) 预处理和每次查询,主要是利用倍增的思想,维护最值,详细算法请看倍增专题。

模板题 题目来源:【POJ 3264】Balanced Lineup
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
int t1[50010][20], t2[50010][20];
int main(){
    int n, m, q;
    scanf("%d%d", &n, &q);
    m = log((n))/log(2.0);
    for(int i = 1; i <= n; i ++) scanf("%d", &t1[i][0]), t2[i][0] = t1[i][0];
    for(int j = 1; j <= m; j ++){
        for(int i = 1; i <= n; i ++){
            t1[i][j] = t1[i][j-1], t2[i][j] = t2[i][j-1];
            if(i+(1<<(j))-1<= n){
                t1[i][j] = max(t1[i][j-1], t1[i+(1<<(j-1))][j-1]);
                t2[i][j] = min(t2[i][j-1], t2[i+(1<<(j-1))][j-1]);
            } 
        }
    }
    for(int i = 1; i <= q; i ++){
        int a, b;
        scanf("%d%d", &a, &b);
        int p =(log((b-a+1))/log(2.0));
        printf("%d\n", max(t1[a][p], t1[b-(1<<p)+1][p]) - min(t2[a][p], t2[b-(1<<p)+1][p]));
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值