题1.八皇后:
P1219 [USACO1.5]八皇后 Checker Challenge - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
n*n的棋盘,即dfs有n层,每层有n个分支;
dfs传入当前在哪一行这一个参数,每次查找当前行能用的棋子再继续递归向下;
为控制每列,每条对角线上只有一个棋子,给定两个bool数组记录每列和每个对角线上是否有棋子。
图示:
代码如下:
#include<iostream>
using namespace std;
const int N = 15;
bool st[N];
int dx[2 * N], dy[2 * N],n,path[N],res=0;
void dfs(int u) {
if (u == n) {
res++;
if (res < 4) {
for (int i = 0; i < n; i++)
cout << path[i] << " ";
cout << endl;
}
return;
}
for (int i = 0; i < n; i++) {
if (!st[i]&&!dx[u+i]&&!dy[u-i+N]) {
st[i] = true;
dx[u + i] = true;
dy[u - i + N] = true;
path[u] = i+1;
dfs(u + 1);
st[i] = false;
dx[u + i] = false;
dy[u - i + N] = false;
}
}
}
int main() {
cin >> n;
dfs(0);
cout<<res;
return 0;
}
题2.kkksc03考前临时抱佛脚:
P2392 kkksc03考前临时抱佛脚 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
每个科目最多20题,二进制枚举所有情况,时间复杂度O(2^20),一共四科,总体O(2^22)。
代码如下:
#include<iostream>
#include<cmath>
using namespace std;
const int N = 25,INF=1e9;
int q[N], res = 0,cur=INF;
int main() {
int a[4];
int l, r;
for (int i = 0; i < 4; i++)
cin >> a[i];
for (int i = 0; i < 4; i++) {
cur = INF;
for (int u = 0; u < a[i]; u++)
cin >> q[u];
for (int j = 0; j < pow(2, a[i]); j++) {
l = r = 0;
for (int k = 0; k < a[i]; k++) {
if (j >>k& 1)l += q[k];
else r += q[k];
}
int st = max(l, r);
cur = min(cur, st);
}
res += cur;
}
cout << res;
return 0;
}
题3.马的遍历:
P1443 马的遍历 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
dfs或者bfs均可。
代码如下:
#include<iostream>
#include<queue>
#include<cstring>
#define x first
#define y second
const int N = 410;
int res[N][N];
int a, b, n, m;
using namespace std;
typedef pair<int, int>PII;
int bfs(PII start) {
queue<PII>p;
p.push(start);
int dx[] = { -2,-2,-1,-1,1,1,2,2 };
int dy[] = { 1,-1,2,-2,2,-2,1,-1 };
while (p.size()) {
PII t = p.front();
p.pop();
for (int i = 0; i < 8; i++) {
int x = dx[i] + t.x, y = dy[i] + t.y;
if (x<1 || x>n || y<1 || y>m)continue;
if (res[x][y] != -1)continue;
res[x][y] = res[t.x][t.y] + 1;
p.push({ x, y });
}
}
return -1;
}
int main() {
cin >> n >> m >> a >> b;
PII start = { a,b };
memset(res, -1, sizeof(res));
res[a][b] = 0;
bfs(start);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++)
printf("%-5d", res[i][j]);
cout << endl;
}
}
题4.奇怪的电梯:
P1135 奇怪的电梯 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
bfs最小步数模型问题;
将楼层数看做点,按一次按钮看做一条连接两个点的边,求起点到终点的最短路。
代码如下:
#include<iostream>
using namespace std;
const int N=210;
int qq[N],hh=0,tt=-1;
int q[N];
bool st[N];
int d[N];
int main(){
int n,a,b;
cin>>n>>a>>b;
for(int i=1;i<=n;i++)cin>>q[i];
qq[++tt]=a;
st[a]=true;
while(hh<=tt){
int t=qq[hh++];
if(t+q[t]<=n&&!st[t+q[t]]){qq[++tt]=t+q[t];st[t+q[t]]=true;d[t+q[t]]=d[t]+1;}
if(t-q[t]>=1&&!st[t-q[t]]){qq[++tt]=t-q[t];st[t-q[t]]=true;d[t-q[t]]=d[t]+1;}
if(t+q[t]<=n&&t+q[t]==b){cout<<d[t+q[t]];return 0;}
if(t-q[t]>=1&&t-q[t]==b){cout<<d[t-q[t]];return 0;}
}
cout<<"-1";
return 0;
}
题5.Meteor showers S:
P2895 [USACO08FEB]Meteor Shower S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
预处理处所有不安全点变成焦土的时间,再对起点进行bfs即可。
代码如下:
#include<iostream>
#include<queue>
#include<cstring>
#define x first
#define y second
using namespace std;
typedef pair<int, int>PII;
const int N = 310;
int t[N][N], g[N][N];//贝茜到达的时间和流星雨到达的时间
bool st[N][N];
int n;//n个流星
int dx[] = { 1,0,-1,0 ,0}, dy[] = { 0,1,0,-1 ,0};
int bfs() {
if (g[0][0] == -1)return 0;
queue<PII>q;
q.push({ 0,0 });
while (q.size()) {
auto p = q.front();
q.pop();
for (int i = 0; i < 4; i++) {
int a = p.x + dx[i],b=p.y + dy[i];
int u = t[p.x][p.y] + 1;
if (g[a][b]!=-1&&u >= g[a][b])continue;//流星到达后到
if (a < 0||b<0)continue;//越界
if (st[a][b])continue;
st[a][b] = true;
t[a][b] = t[p.x][p.y] + 1;
q.push({ a,b });
if (g[a][b] == -1)return t[a][b];
}
}
return -1;
}
int main() {
memset(g, -1, sizeof t);//g为-1时该点没有流星
cin >> n;
int a, b, c;
while (n--) {//读入流星
cin >> a >> b >> c;
for (int i = 0; i < 5; i++) {
if (a + dx[i] < 0 ||b + dy[i] < 0)continue;
if (g[a+dx[i]][b+dy[i]] != -1)g[a + dx[i]][b + dy[i]] = min(g[a + dx[i]][b + dy[i]], c);
else g[a + dx[i]][b + dy[i]] = c;
}
}
cout << bfs();
return 0;
}
题6.选数:
P1036 [NOIP2002 普及组] 选数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
试除法判断是否为质数;
数据范围为n小于等于20,二进制枚举和dfs均可,给出dfs代码。
代码如下:
#include<iostream>
using namespace std;
const int N = 25;
bool st[N];//状态数组
int a[N];
int n, k, res = 0;
bool is_prime(int x) {
for (int i = 2; i <= x / i; i++)
if (x % i == 0)return false;
return true;
}
void dfs(int u) {
if (u == n) {//递归结束
int cnt=0,tmp=0;
for (int i = 0; i < n; i++)
if (st[i]) {
cnt++; tmp += a[i];
}
if (cnt == k && is_prime(tmp))res++;//如果选了k个数并且和为素数
return;//回溯
}
st[u] = true;//选
dfs(u + 1);
st[u] = false;//不选
dfs(u + 1);
}
int main() {
cin >> n >> k;
for (int i = 0; i < n; i++)cin >> a[i];
dfs(0);
cout << res;
return 0;
}
题7.PERKET:
P2036 [COCI2008-2009#2] PERKET - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
数据范围为n小于等于10,同样使用dfs或者二进制枚举均可 ,给出dfs代码。
代码如下:
#include<iostream>
#include<algorithm>
#define x first
#define y second
using namespace std;
const int N = 15;
bool st[N];
pair<int, int> p[N];
int n,res=0x3f3f3f3f;
void dfs(int u) {
if (u == n) {
int a=1, b=0,cnt=0;
for(int i=0;i<n;i++)
if (st[i]) { cnt++; a *= p[i].x, b += p[i].y; }
if(cnt)res = min(res, abs(a - b));
return;
}
st[u] = true;
dfs(u + 1);
st[u] = false;
dfs(u + 1);
}
int main() {
cin >> n;
int a, b;
for (int i = 0; i < n; i++) {
cin >> a >> b;
p[i] = { a,b };
}
dfs(0);
cout << res;
return 0;
}
题8.吃奶酪:
P1433 吃奶酪 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
暴力枚举奶酪的所有排列(若当前距离已经大于找到的最短距离,则剪掉当前的这枝);
数据范围为n于小等于15,不剪枝时间复杂度为O(n!);
洛谷题解很多用的状态压缩dp,不会,暴力法最后TLE3个点。
代码如下:
#include<iostream>
#include<vector>
#include<cmath>
#define x first
#define y second
using namespace std;
typedef pair<double,double> PII;
const int N=20;
const int INF=0x3f3f3f;
bool st[N];
double res=INF;
int n;
vector<PII>v;
//s到e的距离
double get(PII s,PII e){
double a=(double)s.x-(double)e.x;
double b=(double)s.y-(double)e.y;
return sqrt(a*a+b*b);
}
//层数、当前的路径长度 、当前点的坐标
void dfs(int u,double cnt,PII s){
if(cnt>=res)return;
if(u==n){res=cnt;return;}
for(int i=0;i<n;i++){
if(!st[i]){
double tmp=get(s,v[i]);//距离
st[i]=true;
cnt+=tmp;
dfs(u+1,cnt,v[i]);
st[i]=false;
cnt-=tmp;
}
}
return;
}
int main(){
cin>>n;
for(int i=0;i<n;i++){
double a,b;
cin>>a>>b;
v.push_back({a,b});
}
dfs(0,0,{0,0});
printf("%.2lf",res);
return 0;
}
题9.迷宫:
P1605 迷宫 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
每个方格最多走一次,求路径方所有案数之和;
从起点开始做一次完整的做dfs,遇到终点则方案数加一,并且回溯到上一层。
代码如下:
#include<iostream>
#define x first
#define y second
using namespace std;
typedef pair<int, int>PII;
PII S, E;//起点和终点
const int N = 15;
int g[N][N];//迷宫
int st[N][N];//每个点的状态
int sx, sy, ex, ey;//起点和终点
int n, m, k;//n*m,k组数据
int res = 0;//走法
int dx[] = { 1,0,-1,0 }, dy[] = { 0,1,0,-1 };
void dfs(int u, PII s) {
if (s == E) {//如果走到了
res++;
return;
}
//cout << s.x << " " << s.y << endl;
for (int i = 0; i < 4; i++) {
int a = s.x + dx[i], b = s.y + dy[i];
if (a <=0 || a > n || b <= 0 || b > m)continue;//越界
if (g[a][b] == 1)continue;//有障碍
if (st[a][b])continue;//走过了
st[s.x][s.y] = true;//选该点
dfs(u + 1, { a,b });//下一层
st[a][b] = false;//恢复操作
}
}
int main() {
cin >> n >> m >> k >> sx >> sy >> ex >> ey;
S = { sx,sy }, E = { ex,ey };
int a, b;
while (k--) {//读入障碍
cin >> a >> b;
g[a][b] = 1;
}
dfs(0, S);//dfs,0层,从s开始搜
cout << res;
return 0;
}
题10.单词接龙:
P1019 [NOIP2000 提高组] 单词接龙 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
dfs求全排列即可;
数据范围为n小于等于20,但每个单词可以用两次,最坏时间复杂度为O(40!)(若数据给20个abbbba就TLE了);
在dfs之前,先预处理出所有单词两两重叠的长度。
代码如下:
#include<iostream>
using namespace std;
const int N=25;
string str[N];
int g[N][N];
int st[N];
int n;
int res=0;
void dfs(int a,int ans){
res=max(res,ans);
st[a]++;
//cout<<"第"<<a<<"个字符串 长度为"<<ans<<" "<<st[a]<<endl;
for(int i=1;i<=n;i++){
if(st[i]==2)continue;
if(!g[a][i])continue;
dfs(i,ans+str[i].size()-g[a][i]);
st[i]--;
}
}
int main(){
cin>>n;
for(int i=1;i<=n;i++)
cin>>str[i];
char start;
cin>>start;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
int a=str[i].size(),b=str[j].size();
for(int k=1;k<a&&k<b;k++)
if(str[i].substr(a-k,k)==str[j].substr(0,k)){g[i][j]=k;break;}
}
for(int i=1;i<=n;i++)
if(str[i][0]==start)dfs(i,str[i].size());
cout<<res;
return 0;
}
题11.单词方阵:
P1101 单词方阵 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
枚举所有单词,当找到y时,分别朝八个方向偏移,如果找到目标字符串,则全部记录下来即可。
代码如下:
#include<iostream>
#include<cstring>
using namespace std;
int dx[]={-1,-1,0,1,1,1,0,-1};
int dy[]={0,1,1,1,0,-1,-1,-1};
const int N=110;
char g[N][N],res[N][N];
string str="yizhong";
int n;
int main(){
cin>>n;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++){
cin>>g[i][j];
res[i][j]='*';
}
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
if(g[i][j]=='y')
for(int k=0;k<8;k++){//往k的方向偏移
if(i+dx[k]*6>=n||i+dx[k]*6<0||j+dy[k]*6>=n||j+dy[k]*6<0)continue;//越界
//判断k[i]方向是否合法
bool flag=true;
for(int p=1;p<=6;p++){//往前走6步
int x=i+dx[k]*p,y=j+dy[k]*p;//当前坐标
char tmp=str[p];//当前应该是的字符串
if(g[x][y]!='*'&&g[x][y]!=tmp){flag=false;break;}
}
//若合法
if(flag){
res[i][j]='y';
for(int p=1;p<=6;p++){//往前走6步
int x=i+dx[k]*p,y=j+dy[k]*p;//当前坐标
res[x][y]=str[p];
}
}
}
for(int i=0;i<n;i++){
for(int j=0;j<n;j++)cout<<res[i][j];
cout<<endl;
}
return 0;
}
题12.自然数拆分问题:
P2404 自然数的拆分问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
数据范围n小于等于8,dfs每层选一个数即可;
由于需要按字典序输出,所以dfs时,下一层选大于等于上一层的数,由于1 1 2和1 2 1是一样的,这样做也可以规避掉重复情况;
代码如下:
#include<iostream>
using namespace std;
const int N = 10;
int path[N];//路径
int n;
void dfs(int u,int s,int k) {//第n层,还差s,上一层选的k
for (int i = k; i <= s; i++) {//寻找这一层选哪个数
s -= i;//选i
path[u] = i;//记录该层选了i
if (s == 0&&u >= 1) {//如果大于等于两个数并且刚好拆完就是输出并且回溯
for (int j = 0; j < u; j++)cout << path[j] << "+";
cout << path[u] << endl;
return;
}
dfs(u + 1, s, i);//继续拆
s += i;//恢复操作
}
}
int main() {
cin >> n;
dfs(0,n,1);
return 0;
}
题13.Lake Counting S:
P1596 [USACO10OCT]Lake Counting S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
bfs求连通块的数量。
代码如下:
#include<iostream>
#include<queue>
#define x first
#define y second
using namespace std;
typedef pair<int, int>PII;//坐标
const int N = 1010;
bool st[N][N];//状态数组
char g[N][N];//图
int n, m;
void bfs(PII s) {
//起点入队
queue<PII>q;
q.push(s);
int dx[] = { 0,1,0,-1 ,-1,1,1,-1},dy[] = { 1,0,-1,0,1,1,-1,-1};//偏移量,注意该题的偏移量有8个
while (q.size()) {//队列不空
//取出队头
auto t = q.front();
q.pop();
for (int i = 0; i < 8; i++) {//遍历该点的上下左右
int a = dx[i] + t.x, b = dy[i] + t.y;
if (a < 0 || a >= n || b<0 || b>=m)continue;//越界
if (st[a][b])continue;//搜过了
if (g[a][b] == '.')continue;//该点位置不是陆地
//如果该点符合要求
st[a][b] = true;//标记
q.push({ a,b });//入队
}
}
return;
}
int main() {
cin >> n >> m;
for (int i = 0; i < n; i++)//读入图
for (int j = 0; j < m; j++)cin >> g[i][j];
int res = 0;
for(int i = 0; i < n; i++)//遍历图
for (int j = 0; j < m; j++)
//如果遍历到的点没搜过并且是陆地
if (!st[i][j] && g[i][j] == 'W') { res++; bfs({ i,j }); }
cout << res;
return 0;
}
题14.填涂颜色
P1162 填涂颜色 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
由题意,闭合圈由数字1构成,即0连通块被1包围需要被染色;
bfs或者dfs,如果搜到该连通块贴着边界则不染色,给出bfs代码。
代码如下:
#include<iostream>
#include<cstring>
#include<queue>
#define x first
#define y second
using namespace std;
typedef pair<int, int>PII;
const int N = 35;
int g[N][N];
bool st[N][N];
int n;
int dx[] = { 1,0,- 1,0 }, dy[] = { 0,1,0,-1 };
bool bfs(PII s, int k) {
memset(st, false, sizeof st);
queue<PII>q;
q.push(s);
st[s.x][s.y] = true;
g[s.x][s.y] = k;//染色
while (q.size()) {
auto t = q.front();
q.pop();
for (int i = 0; i < 4; i++) {
int a = t.x + dx[i], b = t.y + dy[i];
if (a < 0 || a >= n || b < 0 || b >= n) return false;//不是闭合圈
if (g[a][b] == 1) continue;//如果是墙
if (st[a][b])continue;//选过了
//入队并且染色
q.push({ a,b });
st[a][b] = true;
g[a][b] = k;
}
}
return true;
}
int main() {
cin >> n;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
cin >> g[i][j];
bool res = false;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) {
if (g[i][j] != 1) {
res = bfs({ i,j }, 0);//如果不是墙,就判断是否为闭合圈
if (res) { //如果是闭合圈就染成2
bfs({ i,j }, 2);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++)cout << g[i][j]<<" ";
cout << endl;
}
return 0;
}
}
}
return 0;
}
题15.字串变换:
P1032 [NOIP2002 提高组] 字串变换 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
双向bfs:双向BFS_双向广搜_如何何何的博客-CSDN博客
题16.Corn Maze S
P1825 [USACO11OPEN]Corn Maze S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
迷宫问题加了一个传送操作;
bfs时特判该点是否是传送点即可,如果A,B两点是传送门,假设走到了A点,此时相当于走到了B点(将B点加入bfs队列),但是应该标记A点走过了但B点还没走过。
代码如下:
#include<iostream>
#include<queue>
#include<unordered_map>
//define x,y,方便取出pair里的坐标
#define x first
#define y second
using namespace std;
typedef pair<int, int>PII;//坐标
const int N = 310,M=30;
char g[N][N];//迷宫
bool st[N][N];//状态数组,表示该点搜过没有
int d[N][N];//距离起点的距离
int n, m;
//传送点的坐标
PII L[M][2];
int bfs(PII s) {
queue<PII>q;//定义队列
q.push(s);//将起点入队
st[s.x][s.y]=true;
int dx[] = { 0,1,0,-1 }, dy[] = { 1,0,-1,0 };//偏移量
while (q.size()) {//队列不空
//取出队头
auto t = q.front();
q.pop();
for (int i = 0; i < 4; i++) {
int a = dx[i] + t.x,b = dy[i] + t.y;
if (g[a][b] == '#')continue;//如果是走不通
if (st[a][b])continue;//如果搜过了
if (a < 0 || a >= n || b < 0 || b >= m)continue;//如果越界了
//搜到(a,b)
st[a][b] = true;
if(g[a][b]=='.'){d[a][b]=d[t.x][t.y] + 1;q.push({ a,b });}//草地
else if(g[a][b]=='=')return d[t.x][t.y]+1;//终点
else if(g[a][b]>='A'&&g[a][b]<='Z'){//传送门
//传送门的两个坐标
int xx1=L[g[a][b]-'A'][0].x , yy1=L[g[a][b]-'A'][0].y;
int xx2=L[g[a][b]-'A'][1].x , yy2=L[g[a][b]-'A'][1].y;
//在xx1 yy1点
if(a==xx1&&b==yy1){q.push({xx2,yy2});d[xx2][yy2]=d[t.x][t.y]+1;}
//在xx2 yy2点
else{q.push({xx1,yy1});d[xx1][yy1]=d[t.x][t.y]+1;}
}
}
}
return -1;
}
int main() {
for(int i=0;i<M;i++)
L[i][0]=L[i][1]={-1,-1};//初始无传送门
cin >> n >> m;
PII start,end;
for (int i = 0; i < n; i++)//读入迷宫
for (int j = 0; j < m; j++){
cin >> g[i][j];
//传送门
if(g[i][j]>='A'&&g[i][j]<='Z'){
int k=g[i][j]-'A';
if(L[k][0].x==-1)L[k][0]={i,j};
else L[k][1]={i,j};
}
else if(g[i][j]=='@')start={i,j};
}
cout<<bfs(start);//bfs
return 0;
}