2021_GDUT_新生专题训练_数据结构

基础数据结构

链表

基础结构

struct TypeNode {
  int value;
  TypeNode* next;
};

特点:可延展性,失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大.

常见使用方法

push()在末尾添加元素

top()获取栈顶元素

size() 返回栈中元素的个数

pop() 删除第一个元素

empty() 如果栈为空则返回真

扩号匹配问题

在某个字符串(长度不超过100)中有左括号、右括号和大小写字母;规定(与常见的算数式子一样)任何一个左括号都从内到外与在它右边且距离最近的右括号匹配。写一个程序,找到无法匹配的左括号和右括号,输出原来字符串,并在下一行标出不能匹配的括号。不能匹配的左括号用"$“标注,不能匹配的右括号用”?"标注.

input

输入包括多组数据,每组数据一行,包含一个字符串,只包含左右括号和大小写字母,字符串长度不超过100
注意:cin.getline(str,100)最多只能输入99个字符!

output

对每组输出数据,输出两行,第一行包含原始输入字符,第二行由" " , " ? " 和 空 格 组 成 , " ","?"和空格组成," ","?""“和”?"表示与之对应的左括号和右括号不能匹配。

Sample Input

((ABCD(x)
)(rttyy())sss)(

Sample Output

((ABCD(x)
$$
)(rttyy())sss)(
?            ?$

思路:先正序遍历,遇到‘(’便入栈,遇到‘)’就出栈,如果有剩下的便是没有匹配的’(’,入栈的时候是入对应的序号,这样就可以获取的序号,类似的逆序遍历获取便可以得到未匹配的‘)’。&

#include <iostream>
using namespace std;
#include <bits/stdc++.h>
typedef long long ll;
char str[105];
vector <int> l,r;
int main(){
    //std::ios::sync_with_stdio(false);
    while(cin >> str+1){
        l.clear();
        r.clear();
        int len = strlen(str+1);
        for(int i=1;i<=len;i++){
            if(str[i]=='('){
                l.push_back(i);
            }
            else if(str[i]==')'){
                if(!l.empty()) l.pop_back();
            }
        }
        for(int i=len;i>=1;i--){
            if(str[i]==')'){
                r.push_back(i);
            }
            else if(str[i]=='('){
                if(!r.empty()) r.pop_back();
            }
        }
        cout << str+1 << endl;
        int l_c = 0, r_c = r.size()-1;
        for(int i=1;i<=len;i++){
            if(l_c<l.size() && i==l[l_c]) {
                cout << "$";
                l_c++;
            }
            else if( r_c>=0 && i==r[r_c]) {
                cout << "?";
                r_c--;
            }
            else cout <<  " ";
        }
        cout << endl;
    }
}

特点:先进后出

单调栈

借用蓝皮书的话说,借助单调性处理问题的思想在于及时排除不可能的选项,保持策略的集合的高毒性和秩序性。,感觉说人话就是,考虑单调的情况是怎么样的,再思考排除不单调的选项的处理。

经典题目求最大矩形传送门

#include <iostream>
using namespace std;
#include <bits/stdc++.h>
#define IOS std::ios::sync_with_stdio(false);
#define INF 0x3f3f3f3f
typedef long long ll;
typedef unsigned long long ull;
const int N = 1e5+5;
const int mod = 1000000007;
struct rec{ll w;ll h;}a[N];
stack <rec> st;

void solve(int n){    
    if(!n) return ;
    ll ans = 0;
    for(int i = 1; i <= n; i++) cin >> a[i].h, a[i].w = 1;
    a[n+1] = {0,0}; // 最后放多一个零
    for(int i = 1; i <= n+1; i++){
        if(st.empty() || st.top().h < a[i].h) st.push(a[i]);
        else{
            ll wid = 0;
            while(st.size() && st.top().h > a[i].h){
                wid += st.top().w;
                ans = max(ans, wid*st.top().h);
                st.pop();
            }
            a[i].w += wid;
            st.push(a[i]);
        }
    }
    cout << ans << endl;
}

int main(){
    int n;
    cin >> n;
    do{solve(n); cin >> n;}
    while(n);
}

经典接水传送门

#include <iostream>
using namespace std;
#include <bits/stdc++.h>
#define IOS std::ios::sync_with_stdio(false);
#define INF 0x3f3f3f3f
typedef long long ll;
typedef unsigned long long ull;
const int N = 1e5+5;
const int mod = 1000000007;

struct rec{int h; int w;}a[N];
stack <rec> st;

void solve(){
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++)
        cin >> a[i].h, a[i].w = 1;
    a[n+1]  = {0,0};
    int ans = 0;
    for(int i = 1; i <= n; i++){
        if(st.empty() || a[i].h <= st.top().h)
            st.push(a[i]);
        else{
            int wid = 0;
            int h;
            while(st.size() && st.top().h < a[i].h){
                rec top = st.top();
                h = top.h;
                wid += top.w;
                
                ans += (a[i].h - top.h)*top.w;
                st.pop();
            }
            if(st.empty()){
                ans -= wid*(a[i].h - h); // 注意这里减的情况
            }
            a[i].w += wid; st.push(a[i]);
        }
    }
    cout << ans << "\n";
}

int main(){
    solve();
}

队列

常见使用方法

push()在末尾添加元素

front()获取第一个元素

back()获取最后一个元素

size() 返回队列中元素的个数

pop() 删除第一个元素

empty() 如果队列空则返回真

特点:先进先出

队列和栈

队列和栈是两种重要的数据结构,它们具有push k和pop操作。push k是将数字k加入到队列或栈中,pop则是从队列和栈取一个数出来。队列和栈的区别在于取数的位置是不同的。

队列是先进先出的:把队列看成横向的一个通道,则push k是将k放到队列的最右边,而pop则是从队列的最左边取出一个数。

栈是后进先出的:把栈也看成横向的一个通道,则push k是将k放到栈的最右边,而pop也是从栈的最右边取出一个数。

假设队列和栈当前从左至右都含有1和2两个数,则执行push 5和pop操作示例图如下:

push 5 pop

队列 1 2 -------> 1 2 5 ------> 2 5

push 5 pop

栈 1 2 -------> 1 2 5 ------> 1 2

现在,假设队列和栈都是空的。给定一系列push k和pop操作之后,输出队列和栈中存的数字。若队列或栈已经空了,仍然接收到pop操作,则输出error。

input

第一行为m,表示有m组测试输入,m<100。
每组第一行为n,表示下列有n行push k或pop操作。(n<150)
接下来n行,每行是push k或者pop,其中k是一个整数。
(输入保证同时在队列或栈中的数不会超过100个)

ouput

对每组测试数据输出两行,正常情况下,第一行是队列中从左到右存的数字,第二行是栈中从左到右存的数字。若操作过程中队列或栈已空仍然收到pop,则输出error。输出应该共2*m行。

Sample Input

2
4
push 1
push 3
pop
push 5
1
pop

Sample Output

3 5
1 5
error
error

模拟题,原谅我用了vector,狗头保命

#include <iostream>
using namespace std;
#include <bits/stdc++.h>
typedef long long ll;
queue  <int> Qu;
vector <int> t;
vector <int> Ve;
int main(){
    //std::ios::sync_with_stdio(false);
    int m;
    cin >> m;
    while(m--){
        int is = 0;//是否error
        Ve.clear();
        while(!Qu.empty()){
                Qu.pop();
        }
        int n;
        cin >> n;
        while(n--){
            char str[100];
            cin >> str+1;
            if(strcmp(str+1,"push")==0){
                int num;
                cin >> num;
                Qu.push(num);
                Ve.push_back(num);
            }
            else if(strcmp(str+1,"pop")==0){
                if(Qu.empty())
                    is = 1;
                else{
                    Qu.pop();
                    Ve.pop_back();
                }
            }
        }
        if(is==0){
            t.clear();
            while(!Qu.empty()){
                t.push_back(Qu.front());
                Qu.pop();
            }
            for(auto i:t){
                cout << i << " ";
            }
                cout << endl;
            for(auto i:Ve){
                cout << i << " ";
            }
                cout << endl;
        }
        else
            cout << "error" << endl << "error" << endl;


    }
}

分块

思维方式:把数组分为sqrt(n)个区块,修改区间的时候把满足整个区块的变化做下区块标记,剩下的暴力一个一个修改,查询的时候先对每个区块进行排序,后在区块中二分查找。

特点:对于存在区间修改 / 区间查询等操作的数组能特升时间复杂度


字典树(前缀树、trie)

trie是一种用于实现字符串快速检索的多叉树结构。trie的每个节点都有若干个字符指针,若在插入或检索字符串时扫描到一个字符c,就沿着当前节点的c字符指针,走向该指针指向的节点。

模板

int trie[N][26], tot = 0; // 假设字典树的元素是26个小写字母, tot可以视为前缀个数
bool end[N];

void insert(string str){ // 插入
    int p = 0; // 根节点
    for(auto i : str){
        int ch = i - 'a';
        if(trie[p][ch] == 0) trie[p][ch] = ++tot; // 没有当前前缀则新增
        p = trie[p][ch] // 进入下一个节点
    }
    end[p] = 1;
}

bool search(string str){
    for(auto i : str){
		p = trie[p][i-'a'];
        if(!p) return false; // 如果当前节点已经为空,说明该字符串肯定不存在,可提前退出
    }
    return end[p];
}

传送门1

把end数组换成cnt;

#include <iostream>
using namespace std;
#include <bits/stdc++.h>
#define IOS std::ios::sync_with_stdio(false);
#define INF 0x3f3f3f3f
typedef long long ll;
typedef unsigned long long ull;
const int N = 2e6+5;
const int mod = 1000000007;
int trie[N][26];
int tot = 1;
int cnt[N];
string str;

void insert(string str){
    int p = 1;
    for(auto i : str){
        int ch = i - 'a';
        if(trie[p][ch] == 0) trie[p][ch] = ++tot;
        p = trie[p][ch];
    }
    cnt[p]++;
}

int search(string str){
    int p = 1, ans = 0;
    for(auto i : str){
        p = trie[p][i-'a'];
        if(p == 0) return ans;
        ans += cnt[p];
    }
    return ans;
}

int main(){
    int n, m;
    cin >> n >> m;
    
    while(n--){
        cin >> str;
        insert(str);
    
    }
    
    while(m--){
        cin >> str;
        int ans = 0;
        cout << search(str) << endl;
    }
    return 0;
}

传送门2

构造相对应的二进制字典树,寻找跟当前异或值最大的数时,每一步要优先尝试沿着与当前位相反的字符指针向下访问

#include <iostream>
using namespace std;
#include <bits/stdc++.h>
#define IOS std::ios::sync_with_stdio(false);
#define INF 0x3f3f3f3f
typedef long long ll;
typedef unsigned long long ull;
const int N = 1e5+5;
const int mod = 1000000007;
ll a[N];
int trie[N*31][2]; int tot = 0;
void insert(ll x){
    int p = 0;
    for(int i = 32; i >= 1; i--){
        int ch = x>>(i-1) & 1;
        if(trie[p][ch] == 0) trie[p][ch] = ++tot;
        p = trie[p][ch];
    }
}
ll search(ll x){
    ll ans = 0;
    int p = 0;
    for(int i = 32; i >= 1; i--){
        int ch = x>>(i-1) & 1;
        if(!trie[p][!ch]){
            p = trie[p][ch];
            ans = (ans<<1) + ch;
        }
        else{
            p = trie[p][!ch];
            ans = (ans<<1) + !ch;
        }
    }
    return ans^x;
}

int main(){
    int n;
    cin >> n;
    ll ans = 0;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
        insert(a[i]);
        ans = max(ans, search(a[i]));
    }
    cout << ans << endl;
    return 0;
}

线段树

特点是区间修改和区间查询,适用于维护具有区间可加性的属性。

//支持单点修改区间查询的线段树
int a[N];
struct Ltree{
    int l, r;
    ll dat;
	#define l(x) t[x].l
    #define r(x) t[x].r
    #define dat(x) t[x].dat
}t[N];
void built(int p, int l, int r){
    l(p) = l, r(p) = r;
    if(l == r){
        dat(p) = a[l];
        return ;
    }
    int mid = (l+r)/2;
    built(p<<1, l, mid);
    built(p<<1|1, mid+1, r);
    dat(p) = dat(p<<1) + dat(p<<1|1);
}
void change(int p, int x, int d){
    if(l(p) == r(p) ){
        if(x == l(p))
        	dat(p) += d;
    	return ;
    }
    int mid = (l(p)+r(p))>>1;
    if(x <= mid)
        change(p<<1, x, d);
   	else
        change(p<<1|1, x, d);
    dat(p) = dat(p<<1) + dat(p<<1|1);
}
ll ask(int p, int l, int r){
    if(l <= l(p) && r >= r(p)) 
        return dat(p);
    int mid = (l+r)>>1;
    ll val = 0;
    if(mid <= l)
		val += ask(p<<1, l, r);
   	if(r > mid)
        val += ask(p<<1|1, l, r);
    return val;
}
//支持单点修改区间查询的线段树,多了个延迟标记
int a[N];
struct Ltree{
    int l, r;
    ll dat, add;
	#define l(x) t[x].l
    #define r(x) t[x].r
    #define dat(x) t[x].dat
    #define add(x) t[x].add
}t[N];
void built(int p, int l, int r){
    l(p) = l, r(p) = r;
    if(l == r){
        dat(p) = a[l];
        return ;
    }
    int mid = (l+r)/2;
    built(p<<1, l, mid);
    built(p<<1|1, mid+1, r);
    dat(p) = dat(p<<1) + dat(p<<1|1);
}
void spread(int p){
    if(add(p)){
        dat(p<<1) += (r(p<<1)-l(p<<1)+1) * add(p);
        dat(p<<1|1) += (r(p<<1|1)-l(p<<1|1)+1) * add(p);
        add(p<<1) += add(p);
        add(p<<1|1) += add(p);
        add(p) = 0;
    }
}
void change(int p, int l, int r, int d){
    if(l <= l(p) && r >= r(p)){
        dat(p) += 1ll* d * (r(p)-l(p)+1);
        add(p) += d;
    	return ;
    }
    spread(p);
    int mid = (l(p)+r(p))>>1;
    if(l <= mid)
        change(p<<1, l, r, d);
   	if(r > mid)
        change(p<<1|1, l, r, d);
    dat(p) = dat(p<<1) + dat(p<<1|1);
}
ll ask(int p, int l, int r){
    if(l <= l(p) && r >= r(p)) return dat(p);
    spread(p);
    int mid = (l(p)+r(p))>>1;
    ll val = 0;
    if(l <= mid)
		val += ask(p<<1, l, r);
   	if(r > mid)
        val += ask(p<<1|1, l, r);  
    return val;
 }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值