目录
[1. 【模板】单调栈](#1. 【模板】单调栈)
[2. 发射站](#2. 发射站)
[3. Largest Rectangle in a Histogram](#3. Largest Rectangle in a Histogram)
[4. 【模板】单调队列/滑动窗口](#4. 【模板】单调队列/滑动窗口)
[5. 质量检测](#5. 质量检测)
[6. 【模板】并查集](#6. 【模板】并查集)
[7. 亲戚](#7. 亲戚)
[8. Lake Counting S](#8. Lake Counting S)
[9. 程序自动分析(离散化)](#9. 程序自动分析(离散化))
[10. 团伙](#10. 团伙)
[11. 食物链](#11. 食物链)
[12. 食物链](#12. 食物链)
[13. 银河英雄传说](#13. 银河英雄传说)
[14. 【模板】字符串哈希](#14. 【模板】字符串哈希)
[15. 兔子与兔子(前缀哈希数组)](#15. 兔子与兔子(前缀哈希数组))
[16. 【模板】字典树/Tire](#16. 【模板】字典树/Tire)
[17. 于是他错误的点名开始了](#17. 于是他错误的点名开始了)
[18. 最大异或对(01Tire)](#18. 最大异或对(01Tire))
单调栈

1. 【模板】单调栈

cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=3e6+10;
int n,arr[N],ret[N];
int main()
{
cin>>n;
for (int i=1;i<=n;++i)cin>>arr[i];
stack<int> st;
//右侧,逆序遍历
for (int i=n;i>=1;--i) {
//查找更大的,>=堆顶的入堆
while (st.size()&&arr[st.top()]<=arr[i])st.pop();
if (st.size())ret[i]=st.top();
st.push(i);
}
for (int i=1;i<=n;++i) {
cout<<ret[i]<<" ";
}
return 0;
}
2. 发射站

cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=1e6+10;
int n,h[N],v[N];
ll sum[N];
int main()
{
cin>>n;
for (int i=1;i<=n;++i)cin>>h[i]>>v[i];
stack<int> st;
//找左侧最近大
for (int i=1;i<=n;++i) {
while (st.size()&&h[st.top()]<=h[i]) st.pop();
if (st.size()) {
sum[st.top()]+=v[i];
}
st.push(i);
}
//清空
while (!st.empty()) {st.pop();}
//找右侧最近大
for (int i=n;i>=1;--i) {
while (st.size()&&h[st.top()]<=h[i]) st.pop();
if (st.size()) {
sum[st.top()]+=v[i];
}
st.push(i);
}
ll ans=0;
for (int i=1;i<=n;++i) {ans=max(ans,sum[i]);}
cout<<ans<<endl;
return 0;
}
3. Largest Rectangle in a Histogram
SP1805 HISTOGRA - Largest Rectangle in a Histogram - 洛谷

cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=1e6+10;
int n;
ll h[N],L[N],R[N];
int main()
{
while (cin>>n&&n) {
for (int i=1;i<=n;++i)cin>>h[i];
//分别左右找最近小
stack<int> st;
for (int i=1;i<=n;++i) {
while (st.size()&&h[st.top()]>=h[i]) st.pop();
if (st.size())L[i]=st.top();
else L[i]=0;
st.push(i);
}
while (!st.empty()) st.pop();
for (int i=n;i>=1;--i) {
while (st.size()&&h[st.top()]>=h[i]) st.pop();
if (st.size())R[i]=st.top();
else R[i]=n+1;
st.push(i);
}
ll ans=0;
for (int i=1;i<=n;++i) {
//以h[i]为顶,R[i]-1和L[i]+1是最大子矩形的边界
ans=max(ans,h[i]*(R[i]-L[i]-1));
}
cout<<ans<<endl;
}
return 0;
}
单调队列
4. 【模板】单调队列/滑动窗口



cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=1e6+10;
int n,k,arr[N];
int main()
{
cin>>n>>k;
for (int i=1;i<=n;++i)cin>>arr[i];
deque<int> q;
//最小值
for (int i=1;i<=n;++i) {
//当然更小的入队
while (q.size()&&arr[q.back()]>=arr[i])q.pop_back();
q.push_back(i);
if (q.back()-q.front()+1>k)q.pop_front();//维护窗口大小
if (i>=k)cout<<arr[q.front()]<<" ";//维护递增队列,队头即最小
}
cout<<endl;
//清空
q.clear();
//最大值
for (int i=1;i<=n;++i) {
while (q.size()&&arr[q.back()]<=arr[i]) q.pop_back();
q.push_back(i);
if (q.back()-q.front()+1>k) q.pop_front();//维护窗口大小
if (i>=k)cout<<arr[q.front()]<<" ";//维护递减队列,队头即最大
}
return 0;
}
5. 质量检测

cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=1e5+10;
int n,m,A[N];
int main()
{
cin>>n>>m;
for (int i=1;i<=n;++i)cin>>A[i];
deque<int> q;
//维护递增队列
for (int i=1;i<=n;++i) {
while (q.size()&&A[q.back()]>=A[i]) q.pop_back();
q.push_back(i);
if (q.back()-q.front()+1>m)q.pop_front();
if (i>=m)cout<<A[q.front()]<<endl;
}
return 0;
}
普通并查集
6. 【模板】并查集

cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=2e5+10,M=1e6+10;
int n,m,fa[N];
int find(int x) {
return x==fa[x]?x:fa[x]=find(fa[x]);
}
int main()
{
cin>>n>>m;
for (int i=1;i<=n;++i)fa[i]=i;
while (m--) {
int z,x,y;cin>>z>>x>>y;
int fx=find(x);
int fy=find(y);
if (z==1) fa[fx]=fy;
else cout<<(fx==fy?"Y":"N")<<endl;
}
return 0;
}
7. 亲戚

cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=5010;
int n,m,p,fa[N];
int find(int x) {
return x==fa[x]?x:fa[x]=find(fa[x]);
}
int main()
{
cin>>n>>m>>p;
for(int i=1;i<=n;i++)fa[i]=i;
while (m--) {
int x,y;cin>>x>>y;
int fx=find(x);
int fy=find(y);
fa[fx]=fy;
}
while(p--) {
int x,y;cin>>x>>y;
int fx=find(x);
int fy=find(y);
if (fx==fy) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}
8. Lake Counting S
P1596 [USACO10OCT] Lake Counting S - 洛谷

bfs也能解,这里用并查集解决这种联通块问题

题中的八个方向我们只需要存储四个方向即可,
cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=110;
char grid[N][N];
int n,m,fa[N*N];
int dx[4]={0,1,1,1},dy[4]={1,1,0,-1};
int find(int x) {
return x==fa[x]?x:fa[x]=find(fa[x]);
}
void un(int x,int y) {
int fx=find(x);
int fy=find(y);
fa[fx]=fy;
}
int main()
{
cin>>n>>m;
for (int i=0;i<n;++i)
for (int j=0;j<m;++j)cin>>grid[i][j];
for (int i=0;i<n*m;++i)fa[i]=i;
for (int i=0;i<n;++i) {
for (int j=0;j<m;++j) {
if (grid[i][j]=='.')continue;
for (int k=0;k<4;++k) {
int x=i+dx[k],y=j+dy[k];
if (y>=0&&grid[x][y]=='W') un(i*m+j,x*m+y);//二维转一维
}
}
}
int ans=0;
for (int i=0;i<n*m;++i) {
int x=i/m,y=i%m;//一维转二维
if (grid[x][y]=='W'&&fa[i]==i)++ans;
}
cout<<ans<<endl;
return 0;
}
9. 程序自动分析(离散化)

数据量太大,空间不够,利用离散化

也就是尽可能缩小数据的大小,且这些映射一一对应原先的数,如此创建数组就不会超出范围
在本题中,可以看到i,j最大值可达1e9,但是只有1e5次操作,一次操作涉及两个数据,也就是说数据量最大才2e5数量级,如果直接创建,会浪费1e9-2e5这么多空间,而且也会创建失败,所以采用离散化操作,先将数据全部存进一个数组,排序后方便去重,然后离散化(用可能小的数映射大的数)。
另外,因为有多组数据,所以在每组数据开头记得清空。
cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=1e5+10;
struct node {
int x,y,e;
}arr[N];
int t,n,pos;
int tmp[N*2],fa[N*2];
unordered_map<int,int> mp;//离散化
int find(int x) {
return x==fa[x]?x:fa[x]=find(fa[x]);
}
void un(int x,int y) {
fa[find(x)]=find(y);
}
bool solve() {
cin>>n;
//注意清空!
pos=0;
mp.clear();
for (int i=1;i<=n;++i) {
cin>>arr[i].x>>arr[i].y>>arr[i].e;
//先存储所有数据
tmp[++pos]=arr[i].x,tmp[++pos]=arr[i].y;
}
//排序->去重
sort(tmp+1,tmp+pos+1);
int cnt=0;
for (int i=1;i<=pos;++i) {
int x=tmp[i];
if (mp.count(x)) continue;
++cnt;
mp[x]=cnt;//离散化,将数据值大的映射成数据值小的
}
//初始化并查集
for (int i=1;i<=cnt;++i)fa[i]=i;
//先将所有相等的信息维护起来,注意两次判断需要分离
for (int i=1;i<=n;++i) {
int x=arr[i].x,y=arr[i].y,e=arr[i].e;
if (e==1)un(mp[x],mp[y]);
}
for (int i=1;i<=n;++i) {
int x=arr[i].x,y=arr[i].y,e=arr[i].e;
if (e==0&& find(mp[x]) == find(mp[y]) )return false;
}
return true;
}
int main()
{
cin>>t;
while(t--) {
if (solve())cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
return 0;
}
扩展域并查集

10. 团伙



cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=1010;
int n,m,fa[N*2];//扩展域
int find(int x) {
return x==fa[x]?x:fa[x]=find(fa[x]);
}
//一定要让朋友域作父节点,因为最后遍历1~n
void un(int x,int y) {
fa[find(y)]=find(x);
}
int main()
{
cin>>n>>m;
//记得初始化两倍的并查集
for(int i=1;i<=n*2;i++)fa[i]=i;
while(m--) {
char op;int a,b;
cin>>op>>a>>b;
if (op=='F')un(a,b);
else {
un(a,b+n);
un(b,a+n);
}
}
int ans=0;
for (int i=1;i<=n;++i) {
if (fa[i]==i)++ans;
}
cout<<ans<<endl;
return 0;
}
11. 食物链



cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=5e4+10;
int n,k,fa[N*3];
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
void un(int x,int y){fa[find(x)]=find(y);}
int main()
{
cin>>n>>k;
//别忘了初始化
for (int i=1;i<=n*3;++i)fa[i]=i;
int ans=0;
while(k--) {
int op,x,y;cin>>op>>x>>y;
if (x>n||y>n)++ans;
else if(op==1) {
//先判断真话假话
//x,y是同类 取反-> x吃y或者y吃x
if (find(x+n)==find(y)||find(y+n)==find(x))++ans;
else {
un(x,y);//同类域
un(x+n,y+n);//捕食域
un(x+2*n,y+2*n);//被捕食域
}
}
else {
//x吃y 取反-> x和y是同类 or y吃x
if (find(x)==find(y)||find(y+n)==find(x))++ans;
else {
un(x+n,y);
un(x,y+2*n);
un(x+2*n,y+n);
}
}
}
cout<<ans<<endl;
return 0;
}
带权并查集*
12. 食物链


权值代表结点到达父节点的距离,最终更新完路径,图应该如下所示(一个父节点挂了一层子节点)


更新x吃y时,x到y的平均期望权值应该是2,而非1,因为要将y看作父节点,x可以吃y那么最初的相距的距离就是2,5,8...取模3就是2.

cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=5e4+10;
int n,k,fa[N],d[N];
int find(int x) {
if (x==fa[x])return x;
int t=find(fa[x]);//递归找到根节点
d[x]+=d[fa[x]];//由顶向下更新路径
return fa[x]=t;//路径压缩,直接挂在根节点下
}
void un(int x,int y,int w) {
int fx=find(x),fy=find(y);
if (fx!=fy) {
fa[fx]=fy;
d[fx]=d[y]+w-d[x];
}
}
int main() {
cin>>n>>k;
for (int i=1;i<=n;++i)fa[i]=i;
int ans=0;
while(k--) {
int op,x,y;cin>>op>>x>>y;
int fx=find(x),fy=find(y);
if (x>n||y>n)++ans;
else if (op==1) {
if (fx==fy&& ((d[y]-d[x])%3+3)%3!=0)++ans;
else un(x,y,0);
}
else {
//只能是d[y]-d[x]对应不等于1,反过来d[x]-d[y]应当对应不等于2
if (fx==fy&& ((d[y]-d[x])%3+3)%3!=1)++ans;
else un(x,y,2);
}
}
cout<<ans<<endl;
return 0;
}
13. 银河英雄传说

可以多创建一个计数数组,用于路径更新,对于同集合内的元素,我们路径显然是更新好的。
当插入新元素,我们只需要将新元素的根节点插入该集合末端,更新距离上就是d[插入集合头节点]+被插入集合元素的个数(该点通过创建cnt数组实现),同时更新被插入元素的cnt数组即可。
插入元素集合中所有元素的路径,将在下次调用find函数且被需要时更正,有点懒标记的意味。


cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=3e4+10;
int t,n=3e4,fa[N],d[N],cnt[N];
int find(int x) {
if (fa[x]==x)return x;
int t=find(fa[x]);
d[x]+=d[fa[x]];
return fa[x]=t;
}
void un(int x,int y) {
int fx=find(x),fy=find(y);
if (fx!=fy) {
//更新根节点的即可,下次find操作会更新需要的路径
fa[fx]=fy;
d[fx]=cnt[fy];
cnt[fy]+=cnt[fx];
}
}
void query(int x,int y) {
int fx=find(x),fy=find(y);
if (fx!=fy) cout<<-1<<endl;
else cout<<abs(d[x]-d[y])-1<<endl;
}
int main() {
cin>>t;
//初始化
for (int i=1;i<=n;++i) {
fa[i]=i;
cnt[i]=1;
}
while(t--) {
char op;int x,y;
cin>>op>>x>>y;
if (op=='M') un(x,y);
else query(x,y);
}
return 0;
}
字符串哈希
14. 【模板】字符串哈希

cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
#define ULL unsigned long long
const int N=1e4+10,P=131;
int n;
ULL a[N];
ULL get_hash(string str) {
ULL ret=0;
for (int i=1;i<=str.size();++i) {
ret=ret*P+str[i-1];
}
return ret;
}
int main() {
cin>>n;
for (int i=1;i<=n;++i) {
string s;cin>>s;
a[i]=get_hash(s);
}
sort(a+1,a+n+1);
int ans=1;//从1开始
for (int i=2;i<=n;++i) {
if (a[i]!=a[i-1])++ans;
}
cout<<ans<<endl;
return 0;
}
15. 兔子与兔子(前缀哈希数组)

cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
#define ULL unsigned long long
const int N=1e6+10,P=131;
int n,m;
ULL a[N],p[N];
string s;
void init_hash() {
p[0]=1;
for (int i=1;i<=n;++i) {
a[i]=a[i-1]*P+s[i-1];
p[i]=p[i-1]*P;
}
}
ULL get_hash(int l,int r) {
return a[r]-a[l-1]*p[r-l+1];
}
int main() {
cin>>s>>m;
n=s.size();
init_hash();
while(m--) {
int l1,r1,l2,r2;
cin>>l1>>r1>>l2>>r2;
ULL x=get_hash(l1,r1),y=get_hash(l2,r2);
if (x==y) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}
Tire树



如此可以简化存储字符串前缀所需要的空间(如果用哈希表,那么字符串的每个前缀都需要村粗一次,浪费时间不说,空间也有浪费,树形结构再加上数组计数,可以大大优化空间与时间)

tree[i][j]代表i号下标节点的j+'a'字符路径可以到达的节点下标

16. 【模板】字典树/Tire

cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=3e6+10;
int t,idx,tree[N][62],p[N];
int get_num(char ch) {
if (ch>='a'&&ch<='z')return ch-'a';
if (ch>='A'&&ch<='Z')return ch-'A'+26;
return ch-'0'+52;
}
void insert(string s) {
int cur=0;
++p[cur];
for (char ch:s) {
int path=get_num(ch);
if (tree[cur][path]==0) {tree[cur][path]=++idx;}//创建新路径,记录抵达的新节点
cur=tree[cur][path];//树形下探
++p[cur];
}
}
int query(string s) {
int cur=0;
for (char ch:s) {
int path=get_num(ch);
if (tree[cur][path]==0) return 0;
cur=tree[cur][path];
}
return p[cur];
}
void solve() {
int n,q;
cin>>n>>q;
// memset(tree,0,sizeof(tree));
// memset(p,0,sizeof(p));
// memset(e,0,sizeof(e));多次操作空间开销太大
for (int i=0;i<=idx;++i)
for (int j=0;j<62;++j)tree[i][j]=0;
for (int i=0;i<=idx;++i)p[i]=0;
idx=0;
for (int i=0;i<n;i++) {
string s;
cin>>s;
insert(s);
}
while (q--) {
string s;
cin>>s;
int ans=query(s);
cout<<ans<<endl;
}
}
int main() {
cin>>t;
while(t--){solve();}
return 0;
}
17. 于是他错误的点名开始了

cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=5e5+10;//注意是字符个数,不是字符串
int n,m,idx,tree[N][26],e[N];
//由于名字不重复直接bool数组 或者 查询成功后修改e值
void insert(string s) {
int cur=0;
for (char ch:s) {
int path=ch-'a';
if (tree[cur][path]==0)tree[cur][path]=++idx;
cur=tree[cur][path];
}
++e[cur];
}
int find(string s) {
int cur=0;
for (char ch:s) {
int path=ch-'a';
if (tree[cur][path]==0) return 0;
cur=tree[cur][path];
}
if (e[cur]) {
int tmp=e[cur];
e[cur]=-1;
return tmp;
}
return e[cur];
}
int main() {
cin>>n;
while(n--) {
string s;
cin>>s;
insert(s);
}
cin>>m;
while (m--) {
string s;
cin>>s;
int flag=find(s);
if (flag>0) {cout<<"OK"<<endl;}
else if (flag==0) {cout<<"WRONG"<<endl;}
else {cout<<"REPEAT"<<endl;}
}
return 0;
}
18. 最大异或对(01Tire)
P10471 最大异或对 The XOR Largest Pair - 洛谷


cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int N=1e5+10;
int n,idx,a[N],tree[N*32][2];
void insert(int x) {
int cur=0;
for (int i=31;i>=0;--i) {
int path=(x>>i)&1;
if (tree[cur][path]==0)tree[cur][path]=++idx;
cur=tree[cur][path];
}
}
int find(int x) {
//查找每一个x,对于这个x来说,他能得到的最大异或值是多少
int ret=0,cur=0;
for (int i=31;i>=0;--i) {
int path=(x>>i)&1;
//贪心找相反的路径
if (tree[cur][path^1]) {
ret |= (1<<i);
cur=tree[cur][path^1];
}
else cur=tree[cur][path];
}
return ret;
}
int main() {
cin>>n;
for (int i=0;i<n;i++) {
cin>>a[i];
insert(a[i]);
}
int ans=0;
for (int i=0;i<n;i++) {
ans=max(ans,find(a[i]));
}
cout<<ans;
return 0;
}
此篇完。。