码蹄集部分题目(2024OJ赛7.10-7.14;单调栈|单调队列+并查集+前缀和|差分+STL)

1🐋🐋造房子(星耀;单调栈)

时间限制:1秒

占用内存:128M

🐟题目思路

我们观察,从左往右看每列的0,如果有相连的,那么就可以将左边列的方案数继承过来,然后加上当前位置的方案数。我们用单调栈来模拟每一列的继承过程,判断方案数,这样,如果单调递增,那么直接集成前边的,如果有低下来的列的情况,那么就pop出不合适的点,根据满足递增情况的点得到方案数。

MT3044 造房子_哔哩哔哩_bilibili

🐟代码

#include<bits/stdc++.h> 
​
using namespace std;
#define int long long
const int N=3600;
int n,a[N][N],f[N],ans;//a,存放数组;f,存放每列最下方的那个1的行数
​
signed main( )
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++) cin>>a[i][j];
    }
    for(int i=1;i<=n;i++)//遍历每行
    {
        stack<pair<int,int> > st;//单调递增栈
        for(int j=1;j<=n;j++)//遍历每列
        {
            if(a[i][j]) f[j]=i;//记录到该行为止,每列的1的最低位置,也就是以当前行为基点,往上能到达的最大高度
            while(!st.empty()&&f[st.top().first]<f[j])//只要是遇到更低的1的列,前边列的都会被pop出来 
            {
                st.pop();
                //cout<<"pop了"<<endl;
            }
            if(st.empty())//栈空
            {
                st.push({j,0});//将0的点入栈
                st.top().second=(i-f[st.top().first])*st.top().first;//高度*长度
                //cout<<i-f[st.top().first]<<st.top().first<<endl;
            }
            else
            {
                pair<int,int> temp=st.top();
                st.push({j,0});
                st.top().second=temp.second+(i-f[st.top().first])*(st.top().first-temp.first);
                //cout<<temp.second<<i-f[st.top().first]<<st.top().first-temp.first<<endl;
            }
            ans+=st.top().second;
            //cout<<st.top().second<<endl;
        }
    }
    cout<<ans;
    return 0;
}

2🐋🐋交换排列(钻石;并查集)

时间限制:1秒

占用内存:64M

🐟题目思路

可以交换位置的这些数的相对位置可以随意变换,这些数就构成一个集合,也就是说集合中的数在这些位置上可以随意排列,那么最大字典序的排列,就是将集合中的数按从大到小排列放入相应位置。所以考虑使用并查集,同时需要大根堆来得到每次的最大数。

MT3055 交换排列_哔哩哔哩_bilibili

🐟代码

#include<bits/stdc++.h> 
​
using namespace std;
const int N=2e5+5;
int n,m,num[N],f[N];
priority_queue<int> q[N];//因为我要得到每个集合里的最大值,所以用大根堆
​
int find(int x)
{
    if(f[x]==x) return x;
    return f[x]=find(f[x]);
}
​
void merge(int i,int j)
{
    int x=find(i),y=find(j);
    if(x!=y) f[x]=y;
}
​
int main( )
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)//输入并初始化数字数组
    {
        cin>>num[i];
        f[i]=i;
    }
    for(int i=1;i<=m;i++)//输入可交换关系,并生成并查集
    {
        int x,y;
        cin>>x>>y;
        merge(x,y);
    }
    for(int i=1;i<=n;i++) q[find(i)].push(num[i]);//这个根所在的集合里有这些数
    for(int i=1;i<=n;i++)
    {
        //得到每个集合中的当前剩余最大数并输出
        int a=find(i);
        cout<<q[a].top()<<" ";
        q[a].pop();
    }
    return 0;
}

3🐋🐋搭积木(钻石;并查集)

时间限制:1秒

占用内存:128M

🐟题目思路

这个使用并查集,将相连的1并入一个集合,如果最后全连接(也就是只有一个集合)并且最后一行的1的个数大于等于2,那么就是Yes,否则就是No。

MT3054 搭积木_哔哩哔哩_bilibili

🐟代码

#include<bits/stdc++.h> 
​
using namespace std;
const int N=1e3+5;
int f[N*N],n,m,cnt=0,root;
char mp[N][N];
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
​
int find(int x)
{
    if(x==f[x]) return x;
    else return f[x]=find(f[x]);
}
​
void merge(int i,int j)
{
    int x=find(i),y=find(j);
    if(x!=y) f[x]=y;
}
​
int convert(int x,int y)//将二维数组转换为一维数组
{
    return (x-1)*m+y;
}
​
int main( )
{
    cin>>n>>m;
    for(int i=1;i<=n*m;i++) f[i]=i;
    for(int i=1;i<=n;i++) cin>>(mp[i]+1);//+1是为了从1开始输入
    //先判断最后一行的1的个数
    for(int i=1;i<=m;i++)
    {
        if(mp[n][i]=='1') cnt++;
    }
    if(cnt<2)
    {
        cout<<"No";
        return 0;
    }
    //处理其他位置
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(mp[i][j]=='0') continue;
            if(!root) root=convert(i,j);//随便找了个点用于在后边判断是否全连通
            //遍历上下左右格子,判断集合情况
            for(int k=0;k<4;k++)
            {
                int x=i+dx[k],y=j+dy[k];
                if(x<1||y<1||x>n||y>m) continue;
                if(mp[x][y]=='1') merge(convert(i,j),convert(x,y));//相连,并入集合
            }
        }
    }
    root=find(root);//找到根节点判断是否全连接
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(mp[i][j]=='1')
            {
                if(find(convert(i,j))!=root)//并没有全连通
                {
                    cout<<"No";
                    return 0;
                }
            }
        }
    }
    cout<<"Yes";
    return 0;
}

4🐋🐋愤怒的象棚(星耀;单调队列+前缀和|差分)

时间限制:1秒

占用内存:128M

🐟题目思路

这道题目考虑使用前缀和得到各区间最大值之和。使用单调队列,要求队列中数据由大到小排列,找到各区间最大值,详情请看注释和视频讲解。

MT3046 愤怒的象棚_哔哩哔哩_bilibili

🐟代码

#include<bits/stdc++.h> 
​
using namespace std;
const int N=5e6+5;
int n,m,A,a[N],b[N],c[N],sumb[N],sumc[N],ans=-0x3f3f3f3f;
//b[i]表示以i结尾的子区间不包含新象,c[i]表示以i结尾的子区间包含新象,sumb和sumc是前缀和
​
void getmax(int n,int m)//处理b数组的单调队列,需要这个队列中的数越来越小
{
    deque<int> q;
    for(int i=1;i<=n;i++)
    {
        if(!q.empty()&&q.front()<=i-m) q.pop_front();//不该在当前区间的去掉他
        while(!q.empty()&&a[i]>=a[q.back()]) q.pop_back();//不满足单调队列的去掉他
        q.push_back(i);
        if(i>=m) b[i]=a[q.front()],sumb[i]=sumb[i-1]+b[i];//b记录当前区间的最大值,sumb记录各区间最大值的前缀和
    }
}
​
void getmax2(int n,int m)//处理c数组的单调队列
{
    deque<int> q;
    for(int i=1;i<=n;i++)
    {
        if(!q.empty()&&q.front()<=i-m) q.pop_front();
        while(!q.empty()&&a[i]>=a[q.back()]) q.pop_back();
        q.push_back(i);
        if(i>=m) c[i]=max(A,a[q.front()]),sumc[i]=sumc[i-1]+c[i];//和b唯一的区别是加入了新象,所以c需要与当前区间最大值进行比较
    }
}
​
int main( )
{
    cin>>n>>m>>A;
    for(int i=1;i<=n;i++) cin>>a[i];
    getmax(n,m);
    getmax2(n,m-1);//因为有一头新象,所以每个区间只需要m-1头象,再加上这头新象就行了
    for(int i=1;i<=n+1;i++)
    {
        if(i<=m) ans=max(ans,sumc[i+m-2]+sumb[n]-sumb[i+m-2]);
        else if(i>=n-m+2) ans=max(ans,sumb[i-1]+sumc[n]-sumc[i-2]);
        else ans=max(ans,sumb[i-1]+sumc[i+m-2]-sumc[i-2]+sumb[n]-sumb[i+m-2]);
    }
    cout<<ans;
    return 0;
}

5🐋🐋区间最大值(钻石;哈希表+STL)

时间限制:1秒

占用内存:256M

🐟题目思路

遍历所有子序列,使用map记录每个数在序列中出现的次数,将区间内出现次数为1的数放入set,由于set的去重性和默认从小到大排序,可以用rbegin函数得到最后一个元素也就是值最大元素的指针,就可以得到结果了。

需要注意的地方是,每次区间滑动,都要对当前区间前一个元素和当前元素进行判断。

MT3047 区间最大值_哔哩哔哩_bilibili

🐟代码

#include<bits/stdc++.h> 
​
using namespace std;
const int N=1e5+5;
int n,k,a[N];
map<int,int> mp;//记录出现次数
set<int> s;//去重
int main( )
{
    cin>>n>>k;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=k;i++) mp[a[i]]++;//记录当前小区间里每个数的出现次数
    for(int i=1;i<=k;i++)
    {
        if(mp[a[i]]==1) s.insert(a[i]);//将出现次数为1的数摘出来
    }
    if(s.empty()) cout<<"-1 ";//如果没有出现次数为1的数(符合要求的数),直接输出-1
    else cout<<*s.rbegin()<<" ";//获取set中最后一个元素(最大值元素)的值
​
    for(int i=k+1;i<=n;i++)
    {
        //我要判断第一个数的个数,因为如果是2,那么在set中将不存在这个数;如果是1,那么要将她从set中去掉;如果是更大的数,那么减去1之后仍然不会是1,就不用放入set
        if(mp[a[i-k]]==1) s.erase(a[i-k]);
        if(mp[a[i-k]]==2) s.insert(a[i-k]);
        mp[a[i-k]]--;
        //对当前这个数字进行处理,因为是用到了上一组的k-1个数,所以这里只需要用当前i这个数入区间即可
        if(mp[a[i]]==0) s.insert(a[i]);
        if(mp[a[i]]==1) s.erase(a[i]);
        mp[a[i]]++;
        if(s.empty()) cout<<"-1 ";
        else cout<<*s.rbegin()<<" ";
    }
    return 0;
}

6🐋🐋交换序列(钻石;并查集)

时间限制:1秒

占用内存:256M

🐟题目思路

和第2题相似,如果i和对应的目标b[i]在一个集合里,那么就可以实现向目标数组的转化,否则不行。

MT3056 交换序列_哔哩哔哩_bilibili

🐟代码

#include<bits/stdc++.h> 
​
using namespace std;
const int N=1e4+5;
int n,f[N],b[N],d[N];
​
int find(int x)
{
    if(x==f[x]) return x;
    return f[x]=find(f[x]);
}
​
void merge(int i,int j)
{
    int x=find(i),y=find(j);
    if(x!=y) f[x]=y;
}
​
int main( )
{
    cin>>n;
    for(int i=1;i<=n;i++) f[i]=i;
    for(int i=1;i<=n;i++) cin>>b[i];//目标数组
    for(int i=1;i<=n;i++) cin>>d[i];//位置转换数组
    for(int i=1;i<=n;i++)//构建并查集
    {
        if(i-d[i]>=1) merge(i,i-d[i]);
        if(i+d[i]<=n) merge(i,i+d[i]);
    }
    for(int i=1;i<=n;i++)
    {
        if(find(i)!=find(b[i]))//当前元素i和目标b[i]不在一个集合里,那么就无法实现
        {
            cout<<-1;
            return 0;
        }
    }
    cout<<1;
    return 0;
}

⭐创作不易,点个赞吧~

⭐点赞收藏不迷路~

  • 15
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值