基础数据结构
链表
基础结构
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];
}
把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;
}
构造相对应的二进制字典树,寻找跟当前异或值最大的数时,每一步要优先尝试沿着与当前位相反的字符指针向下访问
#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;
}