三牧校队训练题目 Solution

前置知识:

  • 搜索
  • 队列
  • 递归
  • (提高难度)记忆化搜索

T1:P1226 【模板】快速幂

暴力想法:a×a 进行 b 次,每次 a×amodp​。

结果:TLE。

正解:

运用二分、倍增的算法,将每次需要乘的 p 分解为:

{p2   pmod2=0p2+1   pmod=1

然后,若担心溢出,则每次都 modp 一下,使得答案永远 <p

代码:

#include<bits/stdc++.h>
using namespace std;
long long n,k;
unsigned long long p;
unsigned long long ans;
unsigned long long tpow(long long k1)
{
if(k1==0)
{
return 1;
}
if(k1%2==0)
{
unsigned long long t=tpow(k1/2)%p;
return (t*t)%p;
}
else
{
unsigned long long t=tpow(k1/2)%p;
return ((t*t)%p*n)%p;
}
}
int main(){
scanf("%d%d%lld",&n,&k,&p);
ans=tpow(k);
printf("%d^%d mod %lld=",n,k,p);
printf("%llu\n",ans%p);
return 0;
}

P1434 [SHOI2002] 滑雪

思路:

首先第一感是暴力深搜。而深搜宗旨是“一条路搜到底,不撞南墙不回头”。所以会浪费大量时间。在本题即为时间超限。

考虑优化时间复杂度。采取记忆化搜索。

记忆化搜索:将搜索到的每个节点后最优结果都存在数组 f 中。

具体:

放在本题中,即为设 fi,j 表示走到 (i,j) 目前最长滑雪区域。

当深搜到 (x,y) 时,都首先判断该节点的 fx,y 数组是否已经被更新,若更新过则直接返回 fx,y,否则通过正常搜索来更新 fx,y

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=105,MAXM=105;
int a[MAXN][MAXM],f[MAXN][MAXM];
int nxt[4][2]={{0,1},{1,0},{-1,0},{0,-1}};
int n,m,ans;
inline int Read()
{
int num=0,r=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
{
r=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
num=num*10+ch-'0';
ch=getchar();
}
return num*r;
}
int solve(int x,int y)
{
if(x<1||x>n||y<1||y>m)
{
return 0;
}
if(f[x][y]!=0)
{
return f[x][y];
}
f[x][y]=1;
for(int i=0;i<4;i++)
{
if(a[x][y]<a[x+nxt[i][0]][y+nxt[i][1]])
{
f[x][y]=max(f[x][y],solve(x+nxt[i][0],y+nxt[i][1])+1);
}
}
return f[x][y];
}
int main(){
n=Read();
m=Read();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
a[i][j]=Read();
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
ans=max(ans,solve(i,j));
}
}
printf("%d\n",ans);
return 0;
}

P1706 全排列问题

  • 经典深搜。

(具体可以查看《啊哈!算法》搜索)

模板深搜,再加上一个数组,book[i] 表示 i 这个数字是否已经被输出。

若没有输出,则输出;若已经输出,则跳过。

正常回溯。

由于思路不易简述,在代码中注释解释。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=15;
bool vis[N];
int a[N],n;
void dfs(int u,int step)
{
if(step>n)//已经输出超过n个,输出后结束
{
for(int i=1;i<=n;i++)
printf("%5d",a[i]);//%5d 即为每个数字占5个位置
putchar('\n');
return;
}
for(int i=1;i<=n;i++)//枚举
{
if(vis[i]) continue;//如果已经处理过
vis[i]=true;//标记为已处理
a[step]=i;
dfs(i,step+1);
vis[i]=false;//回溯
}
return;
}
int main(){
scanf("%d",&n);
dfs(1,1);
return 0;
}

P1149 [NOIP2008 提高组] 火柴棒等式

还是可以参考《啊哈!算法》搜索章。

首先分析题目。

由于数据不大(n24)。考虑暴力枚举。

由于 += 各需要 2 根火柴棒,所以题目就转化为:

每个数字都需要相应的火柴棒数,组成 a,b,c 三个数字,必须用完 n4 根火柴棒,求有多少种方法满足 a+b=c

方法:

枚举简单暴力。

第一层循环枚举 a,第二层循环枚举 b。由于 c=a+b,所以直接算出。

算出 a,b,c 后,算出每个数字需要的火柴棒数量,判断是否等于 n4

代码:

#include<iostream>
#include<cstdio>
int fun(int x)
{
int num=0;
int f[10]={6,2,5,5,4,5,6,3,7,6};//0~9共需要的火柴棒数
while(x/10!=0)
{
num+=f[x%10];
x/=10;
}
num+=f[x];
return num;
}
int main(){
int a,b,c,m,sum=0;
scanf("%d",&m);
for(a=0;a<=1111;a++)
{
for(b=0;b<=1111;b++)
{
c=a+b;
if(fun(a)+fun(b)+fun(c)==m-4)
{
sum++;
}
}
}
printf("%d",sum);
return 0;
}

P1219 [USACO1.5] 八皇后 Checker Challenge

由于每个棋子放下后,行列对角线都不可以放,所以可以用 3 个数组标记。具体如下。

b[i] 表示第 i 列可不可放棋子。

c[i+j]d[i-j+n] 标记对角线。

重点:由于对角线的任意一个点 (x,y) 对于目前节点 (i,j) ,不是 i+j=x+y 就是 ij+n 相等,可以通过此特性来判断。(找规律)

若放在 (i,j)​ 点上,则 b[i]c[i+j]d[i-j+n] 都需要标记。

代码:

#include<iostream>
#include<cstdio>
using namespace std;
int ans,n;//ans是用来记录输出次数,题目只要求输出3次
int a[15];//每一行
bool b[15],c[40],d[40];//标记数组,b数组标记那一列,c和d数组标记对角线
void print()//打印函数
{
for(int j=1;j<=n;j++)
{
printf("%d ",a[j]);
}
puts("");
return;
}
void dfs(int i)//重点:深搜dfs
{
if(i>n)//如果一种情况成立(i已经遍历完每一列所有位置)
{
ans++;//记录+1
if(ans<=3)//如果<=3才输出,否则就是+1而已
{
print();
}
return;
}
for(int j=1;j<=n;j++)//枚举每一列
{
if(!b[j]&&!c[i+j]&&!d[i-j+n])//如果这个点没有被其他皇后给攻击到
{
//标记ing ……
b[j]=true;
c[i+j]=true;
d[i-j+n]=true;
a[i]=j;
dfs(i+1);//继续深搜
//取消标记,回溯ing……
b[j]=false;
c[i+j]=false;
d[i-j+n]=false;
}
}
return;
}
int main(){
scanf("%d",&n);
dfs(1);//记得从1开始
printf("%d\n",ans);
return 0;
}

P1596 [USACO10OCT] Lake Counting S

判断连通块。

解法:

  • 并查集/染色判断

运用二维数组 vis[i][j] 表示 (i,j) 是否属于一个连通块。

每次遇到没有被判断过的节点,都进行染色,最后仅需判断共有多少种颜色即可。

(由于并查集是黄题,所以当然有更简单的方法)

  • 搜索标记

每遇到一个没标记过的点,从此点开始搜索,标记所有搜索到的点。

然后每搜一次答案都加一即可。

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=105;
int n,m,ans;
char ch[MAXN][MAXN];
int nxt[8][2]={{0,1},{1,0},{0,-1},{-1,0},{1,-1},{-1,1},{1,1},{-1,-1}};
bool vis[MAXN][MAXN];
void dfs(int x,int y)
{
vis[x][y]=true;
for(int i=0;i<8;i++)
{
int tx=x+nxt[i][0],ty=y+nxt[i][1];
if(tx<1||tx>n||ty<1||ty>m)
continue;
if(vis[tx][ty]||ch[tx][ty]=='.') continue;
dfs(tx,ty);
}
return;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>ch[i][j];
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(vis[i][j]||ch[i][j]=='.') continue;
dfs(i,j);
ans++;
}
}
printf("%d\n",ans);
return 0;
}

P1605 迷宫

搜索模板题。

从起始点 (sx,sy) 开始暴力深搜(由于数据过小,不需要记忆化),直接打深搜模板,每搜到 (fx,fy) 答案加一。

代码:

#include<iostream>
#include<cstdio>
using namespace std;
int n,m,t;
int a[51][51],book[51][51];
int startx,starty,overx,overy,num;
int x[15][3];
void dfs(int x,int y,int step)
{
int tx,ty;
int next[4][2]={{0,1},{1,0},{-1,0},{0,-1}};
if(x==overx&&y==overy)
{
num++;
return;
}
for(int i=0;i<=3;i++)
{
tx=x+next[i][0];
ty=y+next[i][1];
if(tx<1||tx>n||ty<1||ty>m)
{
continue;
}
if(a[tx][ty]==0&&book[tx][ty]==0)
{
book[tx][ty]=1;
dfs(tx,ty,step+1);
book[tx][ty]=0;
}
}
}
int main(){
cin>>n>>m>>t;
cin>>startx>>starty>>overx>>overy;
for(int i=1;i<=t;i++)
{
cin>>x[i][1];
cin>>x[i][2];
a[x[i][1]][x[i][2]]=1;
}
book[startx][starty]=1;
dfs(startx,starty,1);
cout<<num<<endl;
return 0;
}

P1739 表达式括号匹配

更具体的讲解在这里,不是我写的

栈应用模板题。

因为与解题有关的,只有小括号,也就是说,在处理字符串时,可以无视其他字符。

因为仅有小括号,可以不用栈,用暴力:

枚举每个字符,统计目前左括号跟右括号的个数,若在任意时刻,右括号个数 > 小括号个数,则不满足

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
string s;
int a,b;
int main(){
cin>>s;
for(int i=0;i<s.size();i++)
{
if(a==0&&b==0)
{
if(s[i]==')')
{
puts("NO");
return 0;
}
}
if(b>a)
{
puts("NO");
return 0;
}
if(s[i]=='(')
{
a++;
}
if(s[i]==')')
{
b++;
}
}
if(a==b)
{
puts("YES");
return 0;
}
puts("NO");
return 0;
}

另一种思路:

由于若该表达式合法,则在一个右括号出现后,上一个没被匹配的括号如果不是有剩余的左括号,则该表达式不合法。如果是,则表示这俩括号已被匹配。

根据此性质,可想到用来维护。

代码:

#include<bits/stdc++.h>
using namespace std;
string s;
stack<char> st;
int s_size;
bool check()
{
st.push('#');//避免访问到空栈
for(int i=0;i<s_size;i++)
{
if(s[i]=='@')
break;
if(s[i]!='('&&s[i]!=')') continue;
if(s[i]=='(')
st.push('(');
if(s[i]==')')
{
if(st.top()=='(')
st.pop();
else
return false;
}
}
if(st.top()=='#')
return true;
return false;
}
int main(){
cin >> s;
s_size=s.size();
if(check())
printf("YES\n");
else
printf("NO\n");
return 0;
}

B3616 【模板】队列

考察队列操作。

操作如题意。更细致请查阅oiwiki

插入:q.push(x)

弹出队首:q.pop()

查找队首元素:q.front()

元素个数:q.size()

直接按照上述操作即可。不贴代码。

P1535 [USACO08MAR] Cow Travelling S

记忆化搜索/剪枝。

fi,j,t 为走到 (i,j) 所需要的时间 t 时可以到达终点的路线数。

类似于滑雪那道题。

还是正常深搜,搜到终点则判断:

若到达终点 (x,y) 的时间恰好t,则 fx,y,t1,否则为 0

然后就正常的四个方向搜索 dfs(tx,ty,t-1)

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=105,M=105,T=20;
int n,m,t,stx,sty,spx,spy,f[N][M][T];
char ch[N][M];
int nxt[4][2]={{1,0},{0,1},{-1,0},{0,-1}};
int dfs(int x,int y,int t)
{
if(f[x][y][t]!=-1)
return f[x][y][t];
if(t==0)
{
if(x==spx&&y==spy)
return f[x][y][t]=1;
return f[x][y][t]=0;
}
int tx,ty,tmp=0;
for(int i=0;i<4;i++)
{
tx=x+nxt[i][0];ty=y+nxt[i][1];
if(tx>=1&&tx<=n&&ty>=1&&ty<=m&&ch[tx][ty]=='.')
tmp+=dfs(tx,ty,t-1);
}
return f[x][y][t]=tmp;
}
int main(){
scanf("%d%d%d",&n,&m,&t);
memset(f,-1,sizeof(f));
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>ch[i][j];
scanf("%d%d%d%d",&stx,&sty,&spx,&spy);
printf("%d\n",dfs(stx,sty,t));
return 0;
}
posted @   Atserckcn  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示