0x10 基本数据结构
1.1 栈
1.1.1 进出栈问题
例题CH1101 火车进栈
#include<iostream>
#include<stack>
#include<vector>
using namespace std;
int n;
int cnt = 20;
vector<int> v;
stack<int> s;
void dfs(int cur){
if( !cnt )
return;
if( v.size()==n ){
cnt--;
for(auto i:v){
cout << i;
}
cout << endl;
}
if( !s.empty() ){
v.push_back(s.top());
s.pop();
dfs(cur);
s.push(v.back());
v.pop_back();
}
if( cur<=n ){
s.push(cur);
dfs(cur+1);
s.pop();
}
}
int main(){
cin >> n;
dfs(1);
}
求栈元素的出栈序列方案数相当于第N项的Catalan数 C(N.2N) / N+1
1.1.2 单调栈
例题:POJ2559
#include<iostream>
#include<stack>
#include<vector>
using namespace std;
typedef long long LL;
const int maxn = 100010;
LL s[maxn];
LL w[maxn];//当前位置前有多少个高于位置的矩形(出栈)
int main(){
int n;
cin >> n;
while(n){
int p = 0;
LL x;
LL ans = 0;
for (int i = 0; i <= n; i++){
if(i<n) cin >> x;
else x = 0;
if( !p || x>s[p] ){
s[++p] = x;
w[p] = 1;
}else{
int width = 0;
while( p && s[p]>x ){
width += w[p];
ans = max(ans, 1ll * width * s[p]);
p--;
}
s[++p] = x;
w[p] = width + 1;
}
}
cout << ans << endl;
cin >> n;
}
}
1.2 队列
1.2.2 单调队列
例题 滑动窗口 POJ2823
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 300010;
#define INF 0x3f3f3f3f3
typedef long long LL;
int q[maxn];
LL sum[maxn];
int main(){
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++){
cin >> sum[i];
sum[i] += sum[i - 1];
}
int head = 0, tail = 0;
LL ans = -INF;
for (int i = 1; i <= n; i++){
if( i-q[head]>m ) head++;
ans = max(ans, sum[i] - sum[q[head]]);
while(head<=tail && sum[q[tail]]>=sum[i] )
tail--;
q[++tail] = i;
}
cout << ans << endl;
}
1.3 链表
例题:邻值查找
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
typedef pair<long long, int> PII;
const int maxn = 100010;
PII a[maxn];
int mp[maxn];//映射a排序后的位置
int pre[maxn];
int nex[maxn];
PII ans[maxn];
int main(){
int n;
cin >> n;
for (int i = 1; i <= n; i++){
cin >> a[i].first;
a[i].second = i;
}
sort(a+1, a + n+1);
a[0].first = -1e11;//哨兵
a[n + 1].first = 1e11;
for (int i = 1; i <= n; i++){
mp[a[i].second] = i;
pre[i]=i-1;
nex[i]=i+1;
}
for (int i = n; i >= 2; i--){
int p = mp[i];
int l = pre[p];
int r = nex[p];
long long lv = a[p].first - a[l].first;
long long rv = a[r].first - a[p].first;
if( lv<=rv){
ans[i] = make_pair(lv, a[l].second);
}else if( rv <lv){
ans[i] = make_pair(rv, a[r].second);
}
nex[l] = r;
pre[r] = l;
}
for (int i = 2; i <= n; i++){
cout << ans[i].first << " " << ans[i].second << endl;
}
}
1.4 Hash
例题:POJ3349
常规做法:把所有雪花序列规划为字典序最小的序列(包括翻转情况),然后将所有序列进行排序,判断是否存在相同序列
本题也可用字符串哈希完成,具体方式见书
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
int snows[N][6],idx[N];
//比较序列
bool cmps(int *pa,int *pb){
for (int i = 0; i < 6; i++){
if( *(pa+i)<*(pb+i))
return true;
else if (*(pa+i) > *(pb+i) )
return false;
}
return false;
}
void get_min(int *b){
int a[12];
int k=0;
for (int i = 0; i < 12; i++) a[i]=b[i%6];
for (int i = 1; i < 6; i++){
k = cmps(a + k, a + i) ? k : i;
}
for (int i = 0; i < 6; i++)
b[i] = a[i + k];
}
//排序时比较下标对应序列
bool cmp(int a,int b){
for (int i = 0; i < 6; i++){
if( snows[a][i] < snows[b][i] )
return true;
else if( snows[a][i] > snows[b][i] )
return false;
}
return false;
}
int main(){
ios::sync_with_stdio(false);
cin >> n;
int snow[6], isnow[6];
for (int i = 0; i < n; i++){
for (int j = 0, k = 5; j < 6; j++,k--){
cin >> snow[j];
isnow[k] = snow[j];
}
get_min(snow);
get_min(isnow);
if( cmps(snow,isnow) )
memcpy(snows[i], snow, sizeof snow);
else
memcpy(snows[i], isnow, sizeof isnow);
idx[i] = i;
}
sort(idx, idx + n, cmp);//最小序列排序
for (int i = 1; i < n; i++){
if( !cmp(idx[i],idx[i-1]) && !cmp(idx[i-1],idx[i]) ){
cout << "Twin snowflakes found." << endl;
return 0;
}
}
cout << "No two snowflakes are alike." << endl;
return 0;
}
1.4.2 字符串Hash
取一固定值P,把字符串看作P进制数,并分配一个大于0的数值,代表每种字符。取一·固定值M,求出该P进制数对M的余数,作为该字符串的Hash值。
通常地,P取 133 或 13331,M = 2 64 (即直接使用 unsigned long long 存储Hash值)
#include<cstdio>
#include<cstring>
using namespace std;
typedef unsigned long long ULL;
const int N = 1000010, base = 131;
char str[N];
ULL h[N];
int main(){
scanf("%s", str+1);
int len = strlen(str+1);
//计算字符串哈希值
for (int i = 1; i <= len; i++){
h[i] = h[i - 1] * 131 + str[i]-'a'+1;
printf("%llu" ,h[i]);
}
}
例题:兔子与兔子CH1401
#include <iostream>
#include <string.h>
using namespace std;
typedef unsigned long long ULL;
const int N = 1000010, p = 131;
char str[N];
ULL h[N],pow[N];//power
ULL get(int l,int r){
return h[r] - h[l - 1] * pow[r - l + 1];
}
int main(){
scanf("%s", str + 1);
int n = strlen(str + 1);
pow[0] = 1;//131^0
for (int i = 1; i <= n; i ++ ){
h[i] = h[i - 1] * p + str[i] - 'a' + 1;
pow[i] = pow[i - 1] * p;
}
int m;
scanf("%d", &m);
while(m--){
int l1, r1, l2, r2;
scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
if( get(l1,r1)==get(l2,r2))
puts("Yes");
else
puts("No");
}
}
例题:回文串序列POJ3974
#include <iostream>
#include <string.h>
#include<algorithm>
using namespace std;
typedef unsigned long long ULL;
const int N = 2000010, base = 131;
char str[N];
ULL hl[N], hr[N], p[N];
ULL get(ULL h[],int l,int r){
return h[r] - h[l - 1] * p[r - l + 1];
}
int main(){
int T = 1;
while (scanf("%s", str + 1), strcmp(str + 1, "END")){
int n = strlen(str + 1);
for (int i = n * 2; i; i-=2){
str[i] = str[i / 2];
str[i - 1] = 'a' + 26;
//用其他字符填充字符串,使回文串必能形成奇回文串减少判断
// a#b#a a#b#b#a
}
n *= 2;
p[0] = 1;
for (int i = 1, j = n; i <= n; i++,j--){
hl[i] = hl[i - 1] * base + str[i] - 'a' + 1;
hr[i] = hr[i - 1] * base + str[j] - 'a' + 1;
p[i] = p[i - 1] * base;
}
int res = 0;
for (int i = 1; i <= n; i++){
int l = 0, r = min(i - 1, n - i);
while( l<r ){
int mid = l + r + 1 >> 1;
if( get(hl, i - mid, i - 1) != get(hr, n - ( i + mid) + 1, n - (i + 1) + 1)) r=mid-1;
else l =mid;
}
if( str[i-l] <= 'z') res = max(res, l + 1);
else res = max(res,l);
}
printf("Case %d: %d\n", T ++ , res);
}
return 0;
}
1.5 字符串
1.5.1 kmp算法
1.对字符串A进行自我“匹配”,求出一个数组next,
next[i] 表示:A中以i结尾的非前缀子串 与 A的前缀 能够匹配的最长长度
2.对字符串 A 进行 B匹配,求出一个数组 f,其中 f[i] 表示:B中以 i 结尾的子串与 A 的前缀能够匹配的最长长度
(如果只是单纯字符串匹配用字符串Hash更方便)
例题 Period POJ1961(next数组性质)
#include <iostream>
using namespace std;
const int N = 1000010;
int n;
char str[N];
int nxt[N];
void get_next(){
for (int i = 2, j = 0; i <= n; i ++ ){
while (j && str[i] != str[j + 1]) j = nxt[j];
if (str[i] == str[j + 1]) j ++ ;
nxt[i] = j;
}
}
int main()
{
int T = 1;
while (scanf("%d", &n), n)
{
scanf("%s", str + 1);
get_next();
printf("Test case #%d\n", T ++ );
for (int i = 2; i <= n; i ++ )
{
int t = i - nxt[i];//当t被整除自动成为最小循环元
if (i > t && i % t == 0) printf("%d %d\n", i, i / t);
}
puts("");
}
return 0;
}
kmp模板(找匹配串在模式串的出现位置)
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int maxn = 110;
char s[maxn];//模式串
char p[maxn];//匹配串
int nex[maxn];
int f[maxn];//s中以i结尾的子串与p的前缀能够匹配的最长长度
void getNext(int plen){
nex[1] = 0;
for (int i = 2,j=0; i <= plen; i++){
while( j && p[i]!=p[j+1] ) j = nex[j];
if( p[i]==p[j+1] ) j++;
nex[i] = j;
}
}
void kmp(){
int slen = strlen(s+1);
int plen = strlen(p+1);
getNext(plen);
for (int i = 1, j = 0; i <= slen; i++){
while( j && (j==plen || s[i]!=p[j+1]) ) j=nex[j];
if(s[i]==p[j+1]) j++;
f[i] = j;
if( f[i]==plen ){
cout << i << endl;//出现位置后缀
j = nex[j];
}
}
}
int main(){
scanf("%s", s + 1);//字符串下标从1开始
scanf("%s", p + 1);
kmp();
}