导语
暑假第二次训练赛,选取能做的题来整理
涉及的知识点
线段树、思维、单调栈、搜索
题目
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;
}
本文介绍了2021年牛客暑期多校训练营第二场的编程题目,涵盖了线段树、搜索、单调栈等算法知识点。通过详细解读题目大意,解析思路并提供代码实现,帮助读者理解并掌握相关算法。文章最后提供了部分题目的解题代码,包括字典序搜索、哈希技巧等高级技巧的应用。
910





