【单调栈/队列&并查集&字符串哈希&Tire树】习题集锦

目录

单调栈

[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. 兔子与兔子(前缀哈希数组))

Tire树

[16. 【模板】字典树/Tire](#16. 【模板】字典树/Tire)

[17. 于是他错误的点名开始了](#17. 于是他错误的点名开始了)

[18. 最大异或对(01Tire)](#18. 最大异或对(01Tire))


单调栈

1. 【模板】单调栈

P5788 【模板】单调栈 - 洛谷

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. 发射站

P1901 发射站 - 洛谷

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. 【模板】单调队列/滑动窗口

P1886 【模板】单调队列 / 滑动窗口 - 洛谷

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. 质量检测

P2251 质量检测 - 洛谷

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. 【模板】并查集

P3367 【模板】并查集 - 洛谷

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. 亲戚

P1551 亲戚 - 洛谷

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. 程序自动分析(离散化)

P1955 [NOI2015] 程序自动分析 - 洛谷

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

也就是尽可能缩小数据的大小,且这些映射一一对应原先的数,如此创建数组就不会超出范围

在本题中,可以看到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. 团伙

P1892 [BalticOI 2003] 团伙 - 洛谷

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. 食物链

P2024 [NOI2001] 食物链 - 洛谷

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. 食物链

P2024 [NOI2001] 食物链 - 洛谷

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

更新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. 银河英雄传说

P1196 [NOI2002] 银河英雄传说 - 洛谷

可以多创建一个计数数组,用于路径更新,对于同集合内的元素,我们路径显然是更新好的。

当插入新元素,我们只需要将新元素的根节点插入该集合末端,更新距离上就是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. 【模板】字符串哈希

P3370 【模板】字符串哈希 - 洛谷

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. 兔子与兔子(前缀哈希数组)

P10468 兔子与兔子 - 洛谷

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

P8306 【模板】字典树 / Trie - 洛谷

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. 于是他错误的点名开始了

P2580 于是他错误的点名开始了 - 洛谷

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;
}

此篇完。。

相关推荐
Barkamin2 小时前
冒泡排序的简单实现
java·算法·排序算法
西装没钱买2 小时前
QT组播的建立和使用(绑定特定的网卡,绑定特定IP)
网络·c++·qt·udp·udp组播
独自破碎E2 小时前
【手撕真题】合并区间
算法
big_rabbit05022 小时前
[算法][力扣110]平衡二叉树
数据结构·算法·leetcode
二年级程序员2 小时前
排序(五)“计数排序” 与 “各排序实际用时测量”
c语言·算法·排序算法
Ralph_Y2 小时前
C++:static
开发语言·c++
松☆2 小时前
C++ 程序设计基础:从 Hello World 到数据类型与 I/O 流的深度解析
c++·算法
Book思议-3 小时前
单链表应用:双指针【快慢指针】
数据结构
nimadan123 小时前
海螺AI漫剧2025推荐,解锁沉浸式互动叙事新体验
c++