2021牛客暑期多校训练营2(线段树待补)

本文介绍了2021年牛客暑期多校训练营第二场的编程题目,涵盖了线段树、搜索、单调栈等算法知识点。通过详细解读题目大意,解析思路并提供代码实现,帮助读者理解并掌握相关算法。文章最后提供了部分题目的解题代码,包括字典序搜索、哈希技巧等高级技巧的应用。

2021牛客暑期多校训练营2

导语

暑假第二次训练赛,选取能做的题来整理

涉及的知识点

线段树、思维、单调栈、搜索

链接:2021牛客暑期多校训练营2

题目

A

题目大意:

思路:

代码

C

题目大意:给一个n×m的点阵,每次只能连接相邻两点,在不能构成封闭图形的情况下,进行先后连接,无线连接判负,询问在最佳策略的时候先手能否赢

思路:首先是理解,当只有一行/一列的时候,如果有一人连接后相邻点都连接,下一人只能连接首尾两点,这样就构成了封闭图形,判负,其余看代码

代码

#include <bits/stdc++.h>

using namespace std;
int n,m;
int main() {
    scanf("%d%d",&n,&m);
    if((n%2)&&(m%2))//11,13,33,31
        printf("NO");
    else
        printf("YES");
    return 0;
}

D

题目大意:给出一种玩牌规则,判断哪副赢

思路:按照题意编写即可

代码

#include <bits/stdc++.h>

using namespace std;

void judge(int a1, int b1, int a2, int b2)
{
    if(a1 > b1)
    {
        int t = a1;
        a1 = b1;
        b1 = t;
    }
    if(a2 > b2)
    {
        int t = a2;
        a2 = b2;
        b2 = t;
    }
    if(a1==a2&&b1==b2)
    {
        cout << "tie" << endl;
    }
    else if(a1==2&&b1==8)
    {
        cout << "first" << endl;
    }
    else if(a2==2&&b2==8)
    {
        cout << "second" << endl;
    }
    else if(a1==b1||a2==b2)
    {
        if(a1==b1&&a2!=b2)
        {
            cout << "first" << endl;
        }
        else if(a2==b2&&a1!=b1)
        {
            cout << "second" << endl;
        }
        else if(a1==b1&&a2==b2)
        {
            if(a1<a2)
            {
                cout << "second" << endl;
            }
            else
            {
                cout << "first" << endl;
            }
        }
    }
    else if(a1!=b1&&a2!=b2)
    {
        if((a1+b1)%10>(a2+b2)%10)
        {
            cout << "first" << endl;
        }
        else if((a1+b1)%10<(a2+b2)%10)
        {
            cout << "second" << endl;
        }
        else
        {
            if(b1 > b2)
            {
                cout << "first" << endl;
            }
            else
            {
                cout << "second" << endl;
            }
        }
    }
}
int main()
{
    int t;
    int a1,b1,a2,b2;
    cin >> t;
    while(t--)
    {
        cin >> a1 >> b1 >> a2 >> b2;
        judge(a1,b1,a2,b2);
    }
    return 0;
}

I

题目大意:有两个迷宫,两只企鹅在各自的起点出发,左右对称行走,对每个方向定义一个字母,求出路径最短且字典序最小的路径,输出字母排列

思路:直接暴搜,但本题有几个点需要注意一下


四维数组: 本题由于两只企鹅间有梦幻联动,可能一个不动另一个动,所以需要四维数组来存储,分别使用两个四维数组,一个记录层数(层数为0),另一个是记录方向
字典序搜索: 可以根据给定的字母方向优先搜索
哈希: 本题有独特的哈希方法,将给定的四维坐标通过处理转换成对应数组,首先将 x 1 , y 1 , x 2 , y 2 x_1,y_1,x_2,y_2 x1,y1,x2,y2 转换成 ((((x1×N+y1)×N)+x2)×N+y2),意思是采用N进制将坐标哈希为唯一值,类似于将(3,2,1,2)变成了3212(十进制),然后为了同时记录方向,将原式变为 ((((x1×N+y1)×N)+x2)×N+y2)×4+i,i为方向的值,因为方向有四种取值,在二进制中占两位,所以为了留出这两位,将前面的乘积左移两位


代码

#include <bits/stdc++.h>

using namespace std;
const int N=20;
int Next[4][2]= {1,0,0,-1,0,1,-1,0},f[N][N][N][N],g[N][N][N][N];
string a[N],b[N],path;
queue<tuple<int,int,int,int>>Q;

int mirror(int x) {//获得对称的操作
    if(x==1||x==2)
        return x^3;
    return x;
}

pair<int,int> Move(int x,int y,int i,int m) {//判断下一位置能不能走
    x+=Next[i][0],y+=Next[i][1];
    if(m==0&&(x<0||y<0||x>=N||y>=N||a[x][y]=='#'))
        x-=Next[i][0],y-=Next[i][1];//不能走就原地不动
    if(m==1&&(x<0||y<0||x>=N||y>=N||b[x][y]=='#'))
        x-=Next[i][0],y-=Next[i][1];
    return {x,y};
}

int main() {
    ios::sync_with_stdio(0),cin.tie(0);
    for(int i=0; i<N; i++)
        cin >>a[i]>>b[i];
    Q.emplace(N-1,N-1,N-1,0);//更快的入队操作
    f[N-1][N-1][N-1][0]=1;//初始化起点
    while(!Q.empty()) {
        auto [x1,y1,x2,y2]=Q.front();//获得首
        Q.pop();
        for(int i=0; i<4; i++) {
            auto [nx1,ny1]=Move(x1,y1,i,0);//获得下一位置
            auto [nx2,ny2]=Move(x2,y2,mirror(i),1);//获得对称位置的下一位置
            if(f[nx1][ny1][nx2][ny2]==0) {//如果没走过
                f[nx1][ny1][nx2][ny2]=f[x1][y1][x2][y2]+1;//增加层数
                g[nx1][ny1][nx2][ny2]=((((x1*N)+y1)*N+x2)*N+y2)*4+i;//哈希
                Q.emplace(nx1,ny1,nx2,ny2);//入队
            }
        }
    }
    int x1=0,y1=N-1,x2=0,y2=0;
    cout <<f[x1][y1][x2][y2]-1<<endl;
    while(1) {
        a[x1][y1]=b[x2][y2]='A';//设置走过的路径
        if(f[x1][y1][x2][y2]==1)
            break;
        int G=g[x1][y1][x2][y2];//获得前一状态哈希值
        path+="DLRU"[G%4],G/=4;//解哈希
        y2=G%N;
        G/=N;
        x2=G%N;
        G/=N;
        y1=G%N;
        G/=N;
        x1=G%N;
    }
    reverse(path.begin(),path.end());//翻转路径
    cout <<path<<endl;
    for(int i=0; i<N; i++)
        cout <<a[i]<<" "<<b[i]<<endl;
    return 0;
}

K

题目大意:给出单调栈一些在第几次操作后对应的容量,判断是否存在一个可行序列满足这样的条件,输出可行序列

思路:首先,对于任何一个已知的位置对应的容量,即 b [ i ] = t b[i]=t b[i]=t,此时单调栈中一定是当前未出现数值中最小的前t个数值,可以这样理解,以样例为例, b [ 5 ] = 4 b[5]=4 b[5]=4,代表此时单调栈中存储的是序列中最小的四个数并且升序放置,如果不满足,即存在一个数不为最小四数之一,可能存在的情况只有最大值在栈顶,其余都是小值,设栈顶值为a,但是如果a在栈中,比a小的值时不可能不在栈中的,与前提矛盾

其次,相邻两个操作的容量差,后操作至多比前操作使容量增加1,设前操作后栈顶为a,后操作插入数为b,b>a时,后操作容量+1,b<a时,栈需要弹出,此时最多持平
先构造b序列,以贪心的想法来进行选取,即每次操作后容量+1,如果遇到已有值的b[i],判断是否能通过b[i-1]+1/b[i-1]不变/b[i-1]削弱得到,不能得到即不存在

构造b序列后,对单调栈进行逆向操作,即由最终状态推到初始状态,根据先前的性质即可,比如 b [ n ] = t b[n]=t b[n]=t,代表前t小的数都存在栈中,弹出栈顶,此时栈顶为最后存入的数,之后将b[n-1]与栈容量判断,b[n-1]>栈容量代表还需数据入栈,此时从未访问的数据中从小到大取,放入栈中,b[n-1]<栈容量代表还需弹出,弹出数即为该操作存入数,以此类推,具体看代码

代码

#include <bits/stdc++.h>

using namespace std;
int b[1212121],a[1212121],S[1212121],n,k,cnt,top;
int main() {
    scanf("%d%d",&n,&k);
    while(k--) {//扫描位置值
        int pos;
        scanf("%d",&pos);
        scanf("%d",&b[pos]);
    }
    for(int i=1; i<=n; i++)
        if(!b[i])//如果该位置没有被填充
            b[i]=b[i-1]+1;
        else if(b[i]>b[i-1]+1) {//如果相差值大于2中断,因为不可能出现这样的情况
            printf("-1\n");
            return 0;
        }
    for(int i=n; i>=1; i--) {
        while(b[i]>top)
            S[++top]=++cnt;//++cnt保证小的弹出再把大的弹出
        a[i]=S[top--];
    }
    for(int i=1; i<=n; i++)
        printf("%d ",a[i]);
    return 0;
}

参考文献

  1. 2021牛客暑期多校训练营2 K Stack
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值