P1219 [USACO1.5]八皇后 Checker Challenge
#include<bits/stdc++.h>
using namespace std;
int a[100],b[100],c[100],d[100];
//a數組表示的是行;
//b數組表示的是列;
//c表示的是左下到右上的對角線;
//d表示的是左上到右下的對角線;
int total;//總數:記錄解的總數
int n;//輸入的數,即N*N的格子,全局變量,搜索中要用
int print()
{
if(total<=2)//保證只輸出前三個解,如果解超出三個就不再輸出,但後面的total還需要繼續疊加
{
for(int k=1;k<=n;k++)
cout<<a[k]<<" ";//for語句輸出
cout<<endl;
}
total++;//total既是總數,也是前三個排列的判斷
}
void queen(int i)//搜索與回溯主體
{
if(i>n)
{
print();//輸出函數,自己寫的
return;
}
else
{
for(int j=1;j<=n;j++)//嘗試可能的位置
{
if((!b[j])&&(!c[i+j])&&(!d[i-j+n]))//如果沒有皇后佔領,執行以下程序
{
a[i]=j;//標記i排是第j個
b[j]=1;//宣佈佔領縱列
c[i+j]=1;
d[i-j+n]=1;
//宣佈佔領兩條對角線
queen(i+1);//進一步搜索,下一個皇后
b[j]=0;
c[i+j]=0;
d[i-j+n]=0;
//(回到上一步)清除標記
}
}
}
}
int main()
{
cin>>n;//輸入N*N網格,n已在全局中定義
queen(1);//第一個皇后
cout<<total;//輸出可能的總數
return 0;
}
P1605 迷宮
#include<bits/stdc++.h>
using namespace std;
int n,m,k,x,y,a,b,ans;
int dx[4] = {0,0,1,-1},dy[4] = {1,-1,0,0};
bool vis[6][6];
struct Node{
int x,y,used[6][6];
}sa;
int main()
{
scanf("%d%d%d",&n,&m,&k);
scanf("%d%d%d%d",&x,&y,&a,&b); //雖然這裏可以合併成一個句子,但是由於我是從python轉過來的,建議大家以後寫代碼都設置一個界限,代碼不宜太長
for(int i = 1,aa,bb;i <= k; i++) //大家注意一下,我現在是直接把變量定義在循環裏面。
{
scanf("%d%d",&aa,&bb); //現在我們不能走障礙了。
vis[aa][bb] = 1;
}
queue<Node> q; //定義結構體後,可以直接使用結構體名定義變量或者隊列。
sa.x = x;
sa.y = y; //橫縱座標替換,這樣寫起來方便。
sa.used[x][y] = 1;//標記走過的路徑
q.push(sa);
while(!q.empty())
{
Node now = q.front(); //一起拿出來
q.pop();
for(int i = 0;i < 4; i++)
{
int sx = now.x + dx[i];
int sy = now.y + dy[i];
if( now.used[sx][sy]
|| vis[sx][sy]
|| sx == 0 || sy == 0
|| sx > n || sy > m)
continue; //如果這裏走過,或者這裏是障礙,或者這裏是牆壁,那麼這裏就不能走。
if(sx == a && sy == b)
{
ans++; //如果這裏是終點,那麼結果數量加一
continue;
}
sa.x = sx;
sa.y = sy;
memcpy(sa.used,now.used,sizeof(now.used));
sa.used[sx][sy] = 1; //這裏的操作都是爲了標記路徑
q.push(sa);
}
}
printf("%d",ans);
return 0;
}
bfs 正確代碼的習慣:
1、bfs 不寫 check
2、寫兩個 if,第一個相當於 check,後面跟 continue
3、很少寫 q.push((node){a,b})
,這樣的省略寫法
4、用 dx 和 dy 兩個數組表示上下左右
關於此題:
在結構體中用了記錄路徑的數組防止 TLE
P1019 單詞接龍
#include<bits/stdc++.h>
using namespace std;
const int N=30;
int chu[N];
string t[N],str;
int n,maxx;
int gao(string x,string q)//找是否可以相連
{
for(int i=x.size()-1;i>=0;i--)//倒的找找到一個就退出這樣肯定是最優的
{
if(x[i]==q[0])//如果和匹配開頭一樣就開始找是否合法
{
int l=i;//當前開始的位置
for(int j=0;j<q.size();j++){if(x[l]==q[j])l++;else break;}//如果一樣的話就一直找否則就退出來,必須退!
if(l==x.size())return l-i;//如果找完了就算出有多少個是匹配的這個就是重複的值
}
}
return 0;//沒有就返回0回去;
}
void dfs(int fu,string x)
{
maxx=max(maxx,(int)x.size()-fu);//用字符的長度減去重複的長度
for(int i=1;i<=n;i++)//找接在後面的單詞
{
int duan=0;
if(chu[i]==2)continue;//一個單詞可以用兩次,如果用過兩次就直接跳
duan=gao(x,t[i]);//duan爲他們重疊的長度
if(!duan)continue;//如果爲0沒重疊就直接跳過(很重要!)
chu[i]++;//這個位置標記一下表示用過
dfs(fu+duan,x+t[i]);//重疊的部分相加字符串更新
chu[i]--;//回溯
}
return ;//沒找有一個匹配就返回
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)cin>>t[i];//把每個都存起來
cin>>str;
dfs(0,str);
cout<<maxx;//最大長度
return 0;
}
注意感嘆號部分,那些部分是必須加的(而不是爲了省時間)
P1101 單詞方陣
#include <bits/stdc++.h>
using namespace std;
const int maxn=100+10;
struct node
{
int x,y;
}c[maxn];//記錄路徑
char fz[maxn][maxn],stand[]="yizhong";//fz保存單詞矩陣,stand保存保準的“yizhong”便於匹配
int vis[maxn][maxn];//保存路徑,是否該點爲答案
int dir[][2]={{-1,-1},{-1,0},{-1,1},{0,-1},{0,1},{1,-1},{1,0},{1,1}};//八向的常量數組
void dfs(int x,int y,node c[],int k,int cur)
{
if(cur==7){
for(int i=0;i<7;i++)
vis[c[i].x][c[i].y]=1;
}
else{
int dx=x+dir[k][0];//沿着正確的k方向搜索
int dy=y+dir[k][1];
if(cur==6||fz[dx][dy]==stand[cur+1]){//當cur是6的時候就已經完成了(因爲單詞是7個字母)
c[cur].x=x;c[cur].y=y;
dfs(dx,dy,c,k,cur+1);
}
}
}
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%s",fz[i]); //一次存儲一行
memset(vis,0,sizeof(vis));
for(int i=0;i<n;i++)//搜索y,i相連的可能的方向k,以k爲方向進行DFS
for(int j=0;j<n;j++)
if(fz[i][j]=='y')
for(int k=0;k<8;k++)
{
int x=i+dir[k][0];
int y=j+dir[k][1];
if(fz[x][y]=='i')
dfs(i,j,c,k,0);//注意是從 i j 開始
}
for(int i=0;i<n;i++){//輸出結果
for(int j=0;j<n;j++)
if(vis[i][j]) printf("%c",fz[i][j]);
else printf("*");
printf("\n");
}
return 0;
}
dfs 正確代碼的習慣:
1、寫 dir 二維數組表示方向
關於此題:
1、此代碼定義了結構體數組,用於記錄路徑
2、之所以先找 y 和 i 是因爲要用 k 確定方向(也可以不用先找 y 和 i )
3、字符串一次存一行
P1443 馬的遍歷
#include<bits/stdc++.h>
using namespace std;
struct xy{
int x,y;
}node,Top;
const int dx[4]={1,-1,2,-2};
const int dy[4]={1,-1,2,-2};//雖說一共16個方向 但是在程序中有具體判斷
int a[401][401];
bool b[401][401];
int n,m;
int main(){
memset(b,true,sizeof(b));
memset(a,-1,sizeof(a));
int x,y;
scanf("%d%d%d%d" ,&n ,&m ,&x ,&y );
a[x][y] = 0;
b[x][y] = false;
queue<xy> Q;//構建隊列
node.x = x;
node.y = y;
Q.push(node);//起始點入隊
while (!Q.empty()){
Top=Q.front();//取出隊首點
Q.pop();//隊首點出隊
for (int i=0;i<4;i++)
for (int j=0;j<4;j++)
if (abs(dx[i])!=abs(dy[j])){//判斷方向
int NewX=Top.x+dx[i];
int NewY=Top.y+dy[j];
if (NewX<1||NewX>n||NewY<1||NewY>m) continue;//判斷越界
if (b[NewX][NewY]){//使用布爾數組保證每個點只入隊一次 時間複雜度明顯低於DFS
node.x=NewX;
node.y=NewY;
Q.push(node);
b[NewX][NewY] = false;//標記已入隊
a[NewX][NewY] = a[Top.x][Top.y]+1;//路徑+1
}
}
}
for (int i=1;i<=n;i++){
for (int j=1;j<=m;j++)
printf("%-5d", a[i][j]);//-5d是向左對其,並有 5格
printf("\n");
}
return 0;
}
重點提醒:
不能這樣寫:bool b[405][405]={true};
要這樣寫:fill(b[0],b[0]+401*401,true);
因爲二維數組是不能直接用 true 的
P2895 [USACO08FEB]Meteor Shower S
#include<bits/stdc++.h>
using namespace std;
struct node
{
int x,y,time;
} p; //x,y存座標,time存當前的時間
int m,x,y,t,nx,ny,time1[305][305],c[305][305]; //time1數組存這個格子流星最早到達的時間,c存是否到過這個格子,數組開大點!!!!
int b1[4]= {0,0,1,-1},b2[4]= {1,-1,0,0};
queue<node>q;
int main()
{
cin>>m;
for(int i=0; i<=302; i++)
for(int j=0; j<=302; j++)
time1[i][j]=-1; //先都賦初值爲-1
for(int i=1; i<=m; i++)
{
cin>>x>>y>>t;
if(t<time1[x][y]||time1[x][y]==-1) //這顆流星到達的時間必須小於前面流星或焦土到達的時間,或者還暫時沒有流星及焦土
time1[x][y]=t;
for(int i=0; i<4; i++)
{
nx=x+b1[i],ny=y+b2[i];
if(nx>=0&&ny>=0&&(time1[nx][ny]==-1||t<time1[nx][ny])) //注意是 t,不是 t+1 !
time1[nx][ny]=t; //枚舉焦土
}
}
p.x=0,p.y=0,p.time=0,c[0][0]=1;
q.push(p);
while(!q.empty())
{
p=q.front();q.pop();
for(int i=0; i<4; i++)
{
nx=p.x+b1[i],ny=p.y+b2[i];
if(nx>=0&&ny>=0&&c[nx][ny]==0&&(time1[nx][ny]==-1||p.time+1<time1[nx][ny])) //沒有流星到過或者bessie到這個格子的時候流星還沒有到達
{
node txt;
txt.x=nx,txt.y=ny,txt.time=p.time+1,c[nx][ny]=1; //擴展節點
q.push(txt);
if(time1[nx][ny]==-1) //判斷當前的格子是否安全
{
cout<<txt.time<<endl; //輸出答案
return 0;
}
}
}
}
cout<<-1<<endl; //到不了安全的格子就輸出-1
return 0;
}
加了時間的因素,很強
P2404 自然數的拆分問題
#include<bits/stdc++.h>
using namespace std;
int n, p[11]={1}, m;
void print(int a)
{
for(int i=1; i<a; i++)
cout<<p[i]<<"+";
cout<<p[a]<<endl;
}
void dfs(int a)
{
for(int i=p[a-1]; i<=m; i++)//回溯後跳出分支
{
if(i==n) return;//防止最後一行輸出n
p[a]=i;
m-=i;
if(m==0) print(a);//m減完時,該方案已排列完畢,進行輸出
else dfs(a+1);//否則繼續搜索
m+=i;//回溯
}
}
int main()
{
cin>>n;
m=n;
dfs(1);
return 0;
}
又是在 for 循環初始化數組,這種回溯思路(dfs)非常難想,包括我現在都還沒想懂
P1596 [USACO10OCT]Lake Counting S
//第一種方法:BFS
#include<cstdio>
#include<queue>
using namespace std;
const int maxn = 1e8 + 100;
struct queue
{
int l = 0,r = 0,a[maxn];
void push(int x){
a[r++] = x;
}
int front(){
return a[l];
}
void pop(){
l++;
}
int empty(){
return l >= r ? 1 : 0;
}
}hori,para;//行的隊列 列的隊列
int n,m;
int ans;
char s[101][101];
inline int read(){//讀入優化,可以加快數字的輸入
char p=0;int r=0,o=0;
for(;p<'0'||p>'9';o|=p=='-',p=getchar());
for(;p>='0'&&p<='9';r=(r<<1)+(r<<3)+(p^48),p=getchar());
return o?(~r)+1:r;
}
inline void bfs(int x,int y){//不用遞歸時可以加inline,提高1ms的運行速度
s[x][y]='.';
int dx,dy;
for(int i=-1;i<=1;i++){
for(int j=-1;j<=1;j++){
dx=x+i;
dy=y+j;
if(dx>=0&&dx<n&&dy>=0&&dy<m&&s[dx][dy]=='W'){
hori.push(dx);
para.push(dy);
}
}
}
}
int main(){
n=read();m=read();//看不懂的話可以把這一行改成cin或scanf
for(int i=0;i<n;i++){
scanf("%s",s[i]);
}
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(s[i][j]=='W'){
hori.push(i);
para.push(j);
while(!hori.empty()){//如果隊列不爲空,一個就行了
bfs(hori.front(),para.front());//廣搜隊列前面的元素
hori.pop();para.pop();//彈出元素
}
ans++;
}
}
}
printf("%d",ans);
return 0;
}
//第二種方法:DFS
#include <bits/stdc++.h>
using namespace std;
char a[101][101];
int ans;
int n,m;
void dfs(int x,int y){
a[x][y]='.';
int dx,dy;
for(int i=-1;i<=1;i++){
for(int j=-1;j<=1;j++){
dx=x+i;
dy=y+j;
if(dx>=0&&dx<=n&&dy>=0&&dy<m&&a[dx][dy]=='W'){
dfs(dx,dy);
}
}
}
return;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=0;i<=n;i++){
scanf("%s",a[i]);//避免換行帶來問題這裏直接讀入字符串
}
for(int i=0;i<=n;i++){
for(int j=0;j<m;j++){
if(a[i][j]=='W'){//如果是W的話就直接開始遍歷
dfs(i,j);
ans++;//水潭加一處
}
}
}
printf("%d",ans);
return 0;
}
關於此題 BFS:
1、雙重 for循環代替 dir方向數組
2、雙重 queue存儲座標
3、手寫隊列
4、BFS 不是遞歸
5、既然不是遞歸就加上 inline,可以提高運行速度
關於此題 DFS:
和 BFS 差不多,就是遞不遞歸的區別
P1162 填塗顏色
#include <bits/stdc++.h>
using namespace std;
int a[32][32],b[32][32];
int dx[5]={0,-1,1,0,0};
int dy[5]={0,0,0,-1,1};//第一個表示不動,是充數的,後面的四個分別是上下左右四個方向
int n,i,j;
void dfs(int p,int q){
int i;
if (p<0||p>n+1||q<0||q>n+1||a[p][q]!=0) return;//如果搜過頭或者已經被搜過了或者本來就是牆的就往回
a[p][q]=1;//染色
for (i=1;i<=4;i++) dfs(p+dx[i],q+dy[i]);//向四個方向搜索
}
int main(){
cin>>n;
for (i=1;i<=n;i++)
for (j=1;j<=n;j++){
cin>>b[i][j];//其實不拿兩個數組也可以,不過我喜歡啦
if (b[i][j]==0) a[i][j]=0;
else a[i][j]=2;
}
dfs(0,0);//搜索 從0,0開始搜
for (i=1;i<=n;i++){
for (j=1;j<=n;j++)
if (a[i][j]==0) cout<<2<<' ';//如果染過色以後i,j那個地方還是0,說明沒有搜到,就是周圍有牆,當然就是被圍住了,然後輸出2
else cout<<b[i][j]<<' ';//因爲被染色了,本來沒有被圍住的水和牆都染成了1,所以就輸出b[i][j]
cout<<'\n';//換行
}
}
從邊界(0,0)開始搜索,因爲與邊界連通的塊是不閉合的塊,輸入的塊是從(1,1)開始的
P1032 字串變換
#include <iostream>
#include <string>
#include <cstring>
#include <queue>
#include <map>
#define maxn 15
using namespace std;
struct node{//方便搜索,也可以使用pair簡化
string str;
int step;
}s,v;
string a,b;
string orginal[maxn];
string translated[maxn];
int n,ans;
map<string,int> ma;//很重要的東西,用來判重,否則會TLE在第3點和第5點
string trans(const string &str,int i,int j){//最好加上 const
string ans = "";
if (i+orginal[j].length() > str.length())//位置 + 原字符串的長度加大於要轉換的字符串
return ans;
for (int k=0; k < orginal[j].length();k++)
if (str[i+k] != orginal[j][k])
return ans;
ans = str.substr(0,i);
ans+=translated[j];
ans+=str.substr(i+orginal[j].length());
return ans;
}
void bfs(){//一個平淡無奇的bfs過程
queue <node> q;
s.str = a;
s.step = 0;
q.push(s);
while (!q.empty()){
node u = q.front();
q.pop();
string temp;
if(ma[u.str] == 1) //剪枝,判斷重複的路徑
continue;
if (u.str == b){
ans = u.step;
break;
}
ma[u.str] = 1;
for (int i=0;i < u.str.length();i++)//枚舉當前串所有可能位置
for (int j=0; j<n; j++){//枚舉所有可能手段
temp = trans(u.str,i,j);
if (temp != ""){
v.str = temp;
v.step = u.step+1;
q.push(v);
}
}
}
if (ans > 10 || ans == 0)
cout << "NO ANSWER!" << endl;
else
cout << ans << endl;
}
int main(){
cin >> a >> b;
while (cin >> orginal[n] >> translated[n])
n++;
bfs();
return 0;
}
由題目最小步數,知道要用廣搜
補充:此題代碼只能直接上交評測機,不能運行,因爲 while 是無窮無盡的
P1825 [USACO11OPEN]Corn Maze S
#include<bits/stdc++.h>
using namespace std;
int a[500][500],qx,qy,zx,zy,cz1,cz2,bj[500][500],n,m;
int d[4][2]= {{-1,0},{1,0},{0,-1},{0,1}};
struct node
{
int x;
int y;
int bs;
} ans[101010];
int cz(int x,int y)
{
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
if(!(i==x&&j==y))//注意不能與原點相同
{
if(a[i][j]==a[x][y])//直接判斷匹配
{
cz1=i;
cz2=j;
return 0;
}
}
}
}
}
int bfs()
{
int head=0,tail=1; //用這個代替了隊列
do
{
head++;
if(a[ans[head].x][ans[head].y]>='A'&&a[ans[head].x][ans[head].y]<='Z')//判斷是不是腳踩傳送門
{
cz(ans[head].x,ans[head].y);//找到對應的傳送門座標
ans[head].x=cz1;
ans[head].y=cz2;
}
for(int i=0; i<4; i++)
{
int xx=ans[head].x+d[i][0];
int yy=ans[head].y+d[i][1];
if(a[xx][yy]!=0&&bj[xx][yy]==0)//廣搜
{
bj[xx][yy]=1; //不需要回頭,故直接標記爲 1,此路不再走
tail++;
ans[tail].x=xx;
ans[tail].y=yy;
ans[tail].bs=ans[head].bs+1;
if(xx==zx&&yy==zy)//判目標
{
printf("%d\n",ans[tail].bs);
return 0;
}
}
}
}
while(head<tail);
}
int main()
{
char s;
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
cin>>s;//輸入預處理,預處理的特別棒
if(s=='.')
{
a[i][j]=1;
}
if(s>='A'&&s<='Z')
{
a[i][j]=s;//這裏是一個很重要的點,等下說明
}
if(s=='@')
{
qx=i;
qy=j;
a[i][j]=1;
}
if(s=='=')
{
zx=i;
zy=j;
a[i][j]=1;
}
}
}
bj[qx][qy]=1;//注意一定要標記起點
ans[1].x=qx;
ans[1].y=qy;
bfs();
return 0;
}
意料之外,情理之中的神奇思路…
關於此題 BFS:
1、用 tail 和 head 和 數組(加上 do while )代替了隊列
2、BFS 不走回頭路(迷宮一類問題)
3、直接用數字(ascii 碼)代替了輸入的字符
支票面額
一個採購員去銀行兌換一張 y 元 f 分的支票,結果出納員錯給了 f 元 y 分。採購員用去了 n 分之後才發覺有錯,於是清點了餘額尚有 2y 元 2f 分,問該支票面額是多少?
輸入格式:
輸入在一行中給出小於100的正整數 n
輸出格式:
在一行中按格式 y.f 輸出該支票的原始面額。如果無解,則輸出 No Solution
輸入樣例1:
23
輸出樣例1:
25.51
輸入樣例2:
22
輸出樣例2:
No Solution
//第一種方法:枚舉
# include <stdio.h>
int main(void) {
int n, y, f;
int i, j;
int is=0;
scanf ("%d", &n);
for(i=0; i<100; i++) {
for (j=0; j<=100; j++) {
if (n == 98*i-199*j) {
f = i;
y = j;
is = 1;
break;
}
}
if (1 == is) break;
}
if (0 == is) {
printf("No Solution");
} else {
printf("%d.%d", y, f);
}
return 0;
}
//第二種方法:列表達式
#include<cstdio>
#include<iostream>
using namespace std;
int y,f,n;
int main(){
scanf("%d",&n);
y=(98-n)/3;
f=2*y+1;
if((98-n)%3==0) printf("%d.%d\n",y,f);
else{
for(int i=0;i<100;i++){
for(int j=0;j<100;j++){
if((2*i*100+2*j)==(j*100+i-n)){
printf("%d.%d\n",i,j);
return 0;
}
}
}
printf("No Solution\n");
}
return 0;
}
第一種方法不解釋
第二種方法:
首先,按照正常邏輯思維來說,我們會討論兩種情況:
1.y>n 這就意味着,我們可以推出一組方程(兩個等式):
f==2y;
y-n==2f;
但是,很顯然,我們可以得到這個方程是負數解,錢不可能是負的,所以(按照正常想法),我們自然可以得出一個結論:y要比n小,也就是第二種情況是必然成立的。
2.y<n 這也意味着,我們可以推出一組方程(兩個等式):
f-1==2y;
100+y-n==2f;
按照正常想法,我們自然可以解出方程:y=(98-n)/3;f=2*y+1;
所以我會這樣寫:scanf一個n之後,if((98-n)%3!=0),那我們就可以直接判斷 No solution了,因爲錢的單位不管是元還是分,都不可能是小數,必然是整數。然而,然而…
第一個測試點就是不對。
所以,到底錯在哪裏呢?
這就要回到我們第二種情況最開始的那兩個方程,當我們用掉n分之後,所剩下的2y元和2f分真的和最初的錢數有那樣的一一對應的關係嗎?
並不是,仔細想想平常生活中用現金買東西,我們會發現最後手裏剩下的零錢是一大把的,也就是說,在銀行給我們錢之後,我們沒有花錢之前,我們可以保證,y分的範圍一定是小於100的,但是當我們花掉n分錢後,因爲在這期間有可能會產生找零的各種情況,我們最後所剩下的2f分錢有可能就是大於100的,舉個栗子:n == 97,最開始我們有68元,33分,花掉97分之後,我們剩下66元,136分(我們可以統一到分爲單位 驗證一下),那麼在這種情況裏,我們就不能簡單地寫做:f-1==2y; 100+y-n==2f;
不然這會導致看似 No solution,其實是有解的情況。
我猜,這就是第一個測試點卡我的原因。
寫代碼時候,我們可以直接用兩個for循環完事兒,就不用再管(98-n)%3是否爲0,但是我覺得代碼按照一定邏輯來寫也是可以的,雖然看起來冗餘了一些。先驗證(98-n)%3是否爲0,如果爲0,直接算出來y和f;如果不爲0,再用兩個for循環去尋找我們的y和f,也就是暴力搜索。如果這都找不到,那就是 No solution了。