簡單搜尋練習題OJ
A、守衛棋盤
時間限制: 1 Sec 記憶體限制: 128 MB
題目描述
在一個 n ∗ m 的棋盤上有一些被標記的格子,你的任務是在棋盤上放 置儘量少的皇后,使得每個被標記的格子都在至少一個皇后的的攻擊範圍 之內(被皇后佔據也視為在攻擊範圍之內)。注意,皇后是可以被放在未被 標記的格子上的。
輸入
輸入檔案包含至多 15 組測試資料。對於每組測試資料:
第一行為兩個整數 n,m,代表棋盤的大小,接下來 n 行每行有 m 個 字元,字元 ‘X’ 代表一個被標記的格子,字元 ‘.’ 代表一個未被標記的格子。 所有資料滿足n < 10, m < 10。
輸入檔案的最後一行為一個單獨的 “0”,代表輸入資料的結束。
輸出
對於每組測試資料,輸出測試資料的編號以及至少要放置的皇后數 量。
樣例輸入
8 8 XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX 2 2 X. .X 0
樣例輸出
Case 1: 5 Case 2: 1
對於座標(x,y)
判斷4個方向是否有技巧:橫:x,縱,y,撇:x+y,捺:x-y(注意大於0)
迭代加深,實測最多放5個,當4個不行時直接跳出,這也算一個優化
然後按格子一個個判斷是否放有皇后
#include<cstdio> #include<cstring> using namespace std; const int N=105; int n,m,ans,c; char s[N]; bool a[N][N],v[4][N],f; inline bool ok() { for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(a[i][j]&&!v[0][i]&&!v[1][j]&&!v[2][11+i-j]&&!v[3][i+j]) return 0; return 1; } void dfs(int x,int y,int s) { //printf("%d %d %d %d\n",x,y,s,f); if(x>n) return; if(s==ans) { if(ok())f=1; return; } if(y==m) { dfs(x+1,1,s); //printf("%d %d %d %d\n",x,y,s,f); if(f) return; }else { dfs(x,y+1,s); //printf("%d %d %d %d\n",x,y,s,f); if(f) return; } int v1=v[0][x],v2=v[1][y],v3=v[2][11+x-y],v4=v[3][x+y]; v[0][x]=v[1][y]=v[2][11+x-y]=v[3][x+y]=1; if(y==m) { dfs(x+1,1,s+1); //printf("%d %d %d %d\n",x,y+1,s+1,f); if(f) return; }else { dfs(x,y+1,s+1); //printf("%d %d %d %d\n",x,y+1,s+1,f); if(f) return; } v[0][x]=v1,v[1][y]=v2,v[2][11+x-y]=v3,v[3][x+y]=v4; } int main() { scanf("%d",&n); while(n) { scanf("%d",&m); for(int i=1;i<=n;i++) { scanf("%s",s+1); for(int j=1;j<=m;j++) a[i][j]=s[j]=='X'; } for(ans=1;ans<=5;ans++) { for(int i=1;i<=100;i++) v[0][i]=v[1][i]=v[2][i]=v[3][i]=0; //if(ans==5) break; f=0; dfs(1,1,0); // printf("%d\n",f); if(f) break; } printf("Case %d: %d\n",++c,ans); scanf("%d",&n); } return 0; }
B、神祕的別野
時間限制: 1 Sec 記憶體限制: 128 MB Special Judge
題目描述
布萊克先生是土爾其總統的顧問,因此他也將前往參加北約峰會。他 喜歡單獨居住,遠離商業中心的賓館。於是他在奧瑞契佛加租了一間大的 別墅。但有一件事攪擾了他:儘管大多數的房間都有電燈開關,但這些開 關經常只能控制其它房間的燈而不是自己房間的。但是房產經紀人卻認為 這是一個特色,布萊克先生只能認為當電工們將開關連線到框架上時,他 們是心不在焉的。
有些夜晚,布萊克先生回來的很晚,當他站在門廳中時,所有其它房 間的燈都是關的。因為布萊克先生怕黑,所以他不敢進入黑暗的房間,也 不敢關掉他所在房間的燈。布萊克先生想利用這些接錯位置的開關幫助他 前進。他希望能走到臥室並關掉臥室以外所有的燈。
編寫程式,根據給定的房間說明,考慮當僅有門廳中亮的時候,你如 何從門廳到臥室。不可以進入一個黑暗的房間,只能在房間中關自己房間 的開關,最後時,除了臥室以外的燈其餘的燈都必須關閉,每個房間都只 有一盞燈。如果有幾種辦法可以通往臥室,你必須找出使用步數最少的方法。其中“從一個房間到另一個房間”,“開燈”和“關燈”每個過程算一步。
輸入
輸入檔案第一行包含三個整數 R,D 和 S。R 表示別墅的房間數, 1<=R<=10,D 表示房間之間連線的門數,S 表示別墅中燈的開關數。房間 用數字 1 到 R 標識,1 號房間表示門廳,R 房間表示臥室。接下來的 D 行 每行包含兩個整數 I 和 J,表示房間 I 和房間 J 之間有一扇門連線。接下 來的 S 行每行包含兩個整數 K 和 L,表示房間 K 中有一個開關控制房間 L 中的電燈。
輸出
輸出一行解。如果對於布萊克先生有解,則輸出“Mr.Black needs X Steps.”其中 X 表示來到他的臥室並將所有其它房間的燈關掉所需的最少 步數。如果無解,輸出“Poor Mr. Black! No sleep tonight!”
樣例輸入
3 3 4 1 2 1 3 3 2 1 2 1 3 2 1 3 2
樣例輸出
Mr. Black needs 6 steps.
BFS+狀壓記憶化一下(因為BFS具有最先到達的最優的性質)
#include<cstdio>
const int INF=2e9;
using namespace std;
const int N=20,M=2000;
int n,m,p,l,r,q1[500005],q2[500005],f[N][M],a[N];
int cnt,to[M],nxt[M],he[M];
inline void add(int u,int v)
{
to[++cnt]=v,nxt[cnt]=he[u],he[u]=cnt;
}
int main()
{
scanf("%d%d%d",&n,&m,&p);
for(int i=1;i<=m;i++)
{
int u,v; scanf("%d%d",&u,&v);
add(u,v),add(v,u);
}
for(int i=1;i<=n;i++)
for(int j=0;j<1<<n;j++) f[i][j]=INF;
l=r=1,q1[1]=1,q2[1]=1; f[1][1]=0;
for(int i=1;i<=p;i++)
{
int u,v; scanf("%d%d",&u,&v);
a[u]|=(1<<v-1);
}
while(l<=r)
{
int u=q1[l];
for(int i=1;i<=10;i++)
{
int S=q2[l];
if(a[u]&1<<i-1&&S&1<<i-1) S-=1<<i-1;
else if(a[u]&1<<i-1&&!(S&1<<i-1)) S|=1<<i-1;
if(f[u][S]==INF)
q1[++r]=u,q2[r]=S,
f[u][S]=f[u][q2[l]]+1;
}
if(f[n][1<<n-1]!=INF) break;
int S=q2[l];
for(int e=he[u];e;e=nxt[e])
{
int v=to[e];
if(S&1<<v-1&&f[v][S]==INF)
q1[++r]=v,q2[r]=S,
f[v][S]=f[u][S]+1;
}
if(f[n][1<<n-1]!=INF) break;
l++;
}
if(f[n][1<<n-1]==INF) puts("Poor Mr. Black! No sleep tonight!");
else printf("Mr.Black needs %d Steps.\n",f[n][1<<n-1]);
return 0;
}
C、埃及分數
時間限制: 1 Sec 記憶體限制: 128 MB Special Judge
題目描述
在古埃及,人們使用單位分數的和 (形如 1/a 的, a 是自然數) 表示一 切有理數。如:2/3=1/2+1/6, 但不允許 2/3=1/3+1/3, 因為加數中有相同的。對於一個分數 a/b, 表示方法有很多種,但是哪種最好呢?首先,加數 少的比加數多的好,其次,加數個數相同的,最小的分數越大越好。
如:
• 19/45=1/3 + 1/12 + 1/180
• 19/45=1/3 + 1/15 + 1/45
• 19/45=1/3 + 1/18 + 1/30,
• 19/45=1/4 + 1/6 + 1/180
• 19/45=1/5 + 1/6 + 1/18.
最好的是最後一種,因為 1/18 比 1/180,1/45,1/30,1/180 都大。給出 a,b (0<a<b<1000), 程式設計計算最好的表達方式。
輸入
一行兩個整數 a,b。
輸出
若干個數,自小到大排列,依次是單位分數的分母。
樣例輸入
19 45
樣例輸出
5 6 18
暴力列舉分數個數
然後可以根據之前的決策求出當前允許的上下界,dfs就ok了
#include<cstdio>
#include<iostream>
#define ll long long
using namespace std;
ll gcd(ll a,ll b)
{
return b?gcd(b,a%b):a;
}
const int N=10005;
int ans[N],c[N],l,r,num,mn;
ll x,y,g;
bool f;
void dfs(int t,ll x,ll y)
{
if(t>num)
{
if(!x)
{
if(c[num]<mn)
{
mn=c[num];
for(int i=1;i<=num;i++) ans[i]=c[i];
}
f=1;
}
return;
}
l=max((ll)c[t-1]+1,(y-1)/x+1); //下界(分數小於原來的分數)
r=y*(num-t+1)/x; //上界(手推一下,縮放法)
for(int i=l;i<=r;i++)
{
ll xx=x*i-y,yy=y*i;
g=gcd(xx,yy);
c[t]=i;
dfs(t+1,xx/g,yy/g);
}
}
int main()
{
scanf("%lld%lld",&x,&y);
g=gcd(x,y); x/=g,y/=g;
mn=1LL<<31-1;
while(!f)
num++,dfs(1,x,y);
for(int i=1;i<num;i++) printf("%d ",ans[i]);
printf("%d",ans[num]);
return 0;
}
D、傳染病控制
時間限制: 1 Sec 記憶體限制: 128 MB
題目描述
近來,一種新的傳染病肆虐全球。蓬萊國也發現了零星感染者,為防止該病在蓬萊國大範圍流行,該國政府決定不惜一切代價控制傳染病的蔓延。不幸的是,由於人們尚未完全認識這種傳染病,難以準確判別病毒攜帶者,更沒有研製出疫苗以保護易感人群。於是,蓬萊國的疾病控制中心決定採取切斷傳播途徑的方法控制疾病傳播。經過 WHO(世界衛生組織)以及全球各國科研部門的努力,這種新興傳染病的傳播途徑和控制方法已經研究消楚,剩下的任務就是由你協助蓬萊國疾控中心制定一個有效的控制辦法。
【問題描述】
研究表明,這種傳染病的傳播具有兩種很特殊的性質;
第一是它的傳播途徑是樹型的,一個人X只可能被某個特定的人Y感染,只要Y不得病,或者是XY之間的傳播途徑被切斷,則X就不會得病。
第二是,這種疾病的傳播有周期性,在一個疾病傳播週期之內,傳染病將只會感染一代患者,而不會再傳播給下一代。
這些性質大大減輕了蓬萊國疾病防控的壓力,並且他們已經得到了國內部分易感人群的潛在傳播途徑圖(一棵樹)。但是,麻煩還沒有結束。由於蓬萊國疾控中心人手不夠,同時也缺乏強大的技術,以致他們在一個疾病傳播週期內,只能設法切斷一條傳播途徑,而沒有被控制的傳播途徑就會引起更多的易感人群被感染(也就是與當前已經被感染的人有傳播途徑相連,且連線途徑沒有被切斷的人群)。當不可能有健康人被感染時,疾病就中止傳播。所以,蓬萊國疾控中心要制定出一個切斷傳播途徑的順序,以使盡量少的人被感染。
你的程式要針對給定的樹,找出合適的切斷順序。
輸入
輸入格式的第一行是兩個整數n(1≤n≤300)和p。接下來p行,每一行有兩個整數i和j,表示節點i和j間有邊相連(意即,第i人和第j人之間有傳播途徑相連)。其中節點1是已經被感染的患者。
輸出
只有一行,輸出總共被感染的人數。
樣例輸入
7 6 1 2 1 3 2 4 2 5 3 6 3 7
樣例輸出
3
提示
最最暴力的搜尋,然後在一個最優化剪枝即可
#include<cstdio>
#include<iostream>
using namespace std;
int read()
{
int ret=0; char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9')
ret=(ret<<1)+(ret<<3)+ch-'0',ch=getchar();
return ret;
}
const int N=1005;
int n,m,ans,son[N][N],d[N][N],num1[N],num[N];
int cnt,he[N],to[N],nxt[N],mx;
bool fl[N];
inline void add(int u,int v)
{
to[++cnt]=v,nxt[cnt]=he[u],he[u]=cnt;
}
void dfs(int fa,int u,int dep)
{
son[u][++num[u]]=u;
d[dep][++num1[dep]]=u;
for(int e=he[u];e;e=nxt[e])
{
int v=to[e];
if(v!=fa)
{
dfs(u,v,dep+1);
for(int i=1;i<=num[v];i++)
son[u][++num[u]]=son[v][i];
}
}
mx=max(mx,dep);
}
void dfs1(int dep,int s)
{
//printf("%d %d\n",dep,s);
if(s>ans) return; //最優化剪枝
if(mx<dep)
{
ans=s; return;
}
bool f=0;
for(int i=1;i<=num1[dep];i++)
{
int u=d[dep][i];
if(!fl[u])
{
for(int j=1;j<=num[u];j++)
fl[son[u][j]]=1;
int ss=0;
for(int j=1;j<=num1[dep];j++)
if(!fl[d[dep][j]]) ss++;
dfs1(dep+1,s+ss),f=1;
for(int j=1;j<=num[u];j++)
fl[son[u][j]]=0;
}
}
if(!f) ans=s;
}
int main()
{
//freopen("1.in","r",stdin);
//freopen("1.out","w",stdout);
n=read(),m=read();
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
add(u,v),add(v,u);
}
ans=2e9;
dfs(0,1,1);
dfs1(2,1);
printf("%d\n",ans);
return 0;
}
E、蟲食算
時間限制: 1 Sec 記憶體限制: 128 MB
題目描述
所謂蟲食算,就是原先的算式中有一部分被蟲子啃掉了,需要我們根據剩下的數字來判定被啃掉的字母。來看一個簡單的例子: 43#9865#045 + 8468#6633 44445506978 其中#號代表被蟲子啃掉的數字。根據算式,我們很容易判斷:第一行的兩個數字分別是5和3,第二行的數字是5。 現在,我們對問題做兩個限制: 首先,我們只考慮加法的蟲食算。這裡的加法是N進位制加法,算式中三個數都有N位,允許有前導的0。 其次,蟲子把所有的數都啃光了,我們只知道哪些數字是相同的,我們將相同的數字用相同的字母表示,不同的數字用不同的字母表示。如果這個算式是N進位制的,我們就取英文字母表午的前N個大寫字母來表示這個算式中的0到N-1這N個不同的數字:但是這N個字母並不一定順序地代表0到N-1)。輸入資料保證N個字母分別至少出現一次。 BADC + CRDA DCCC 上面的算式是一個4進位制的算式。很顯然,我們只要讓ABCD分別代表0123,便可以讓這個式子成立了。你的任務是,對於給定的N進位制加法算式,求出N個不同的字母分別代表的數字,使得該加法算式成立。輸入資料保證有且僅有一組解。
輸入
包含4行。第一行有一個正整數N(N<=26),後面的3行每行有一個由大寫字母組成的字串,分別代表兩個加數以及和。這3個字串左右兩端都沒有空格,從高位到低位,並且恰好有N位。
輸出
包含一行。在這一行中,應當包含唯一的那組解。解是這樣表示的:輸出N個數字,分別表示A,B,C……所代表的數字,相鄰的兩個數字用一個空格隔開,不能有多餘的空格。
樣例輸入
5 ABCED BDACE EBBAA
樣例輸出
1 0 3 4 2
提示
【資料規模】 對於30%的資料,保證有N<=10; 對於50%的資料,保證有N<=15; 對於全部的資料,保證有N<=26。
暴力搜尋+剪枝
按位處理,看我程式碼即可
#include<iostream>
#include<cstdio>
using namespace std;
const int N=30;
int n,ans[N];
char s[4][N];
bool v[N],ff;
inline int id(char ch)
{
return ch-'A'+1;
}
void dfs(int x,int y,int jw)
{
if(!x)
{
if(!jw)
{
for (int i=1;i<n;i++) printf("%d ",ans[i]);
printf("%d\n",ans[n]);
ff=1; //暴力彈出
}
return;
}
for(int i=x;i;i--)
{
int t1=ans[id(s[1][i])],t2=ans[id(s[2][i])],t3=ans[id(s[3][i])];
if (t1!=-1&&t2!=-1&&t3!=-1&&(t1+t2)%n!=t3&&(t1+t2+1)%n!=t3) return;
} //如果後面的算式根據已知的結果產生衝突,則彈出
int t1=ans[id(s[1][x])],t2=ans[id(s[2][x])],t3=ans[id(s[3][x])];
if(t1!=-1&&t2!=-1&&t3!=-1)
{
dfs(x-1,1,t1+t2+jw>t3);
return;
}//如果三個數已知,進行下一位
if(t1!=-1&&t2!=-1||t1!=-1&&t3!=-1||t2!=-1&&t3!=-1) //如果兩個數已知,可以退出第3個數
{
if(t1==-1)
{
t1=t3-jw-t2;
if(t1<0)
{
if(v[t1+n]) return;
ans[id(s[1][x])]=t1+n,v[t1+n]=1;
dfs(x-1,1,1);
if(ff) return;
ans[id(s[1][x])]=-1,v[t1+n]=0;
}else
{
if(v[t1]) return;
ans[id(s[1][x])]=t1,v[t1]=1;
dfs(x-1,1,0);
if(ff) return;
ans[id(s[1][x])]=-1,v[t1]=0;
}
}else
if(t2==-1)
{
t2=t3-jw-t1;
if(t2<0)
{
if(v[t2+n]) return;
ans[id(s[2][x])]=t2+n,v[t2+n]=1;
dfs(x-1,1,1);
if(ff) return;
ans[id(s[2][x])]=-1,v[t2+n]=0;
}else
{
if(v[t2]) return;
ans[id(s[2][x])]=t2,v[t2]=1;
dfs(x-1,1,0);
if(ff) return;
ans[id(s[2][x])]=-1,v[t2]=0;
}
}else
if(t3==-1)
{
t3=t1+t2+jw;
if(t3>=n)
{
if(v[t3-n]) return;
ans[id(s[3][x])]=t3-n,v[t3-n]=1;
dfs(x-1,1,1);
if(ff) return;
ans[id(s[3][x])]=-1,v[t3-n]=0;
}else
{
if(v[t3]) return;
ans[id(s[3][x])]=t3,v[t3]=1;
dfs(x-1,1,0);
if(ff) return;
ans[id(s[3][x])]=-1,v[t3]=0;
}
}
return;
}
if(ans[id(s[y][x])]==-1) //如果以上條件都不行,進入列舉階段
{
for(int i=n-1;i>=0;i--)
if(!v[i])
{
ans[id(s[y][x])]=i,v[i]=1,
dfs(x,y+1,jw);
if(ff) return;
ans[id(s[y][x])]=-1,v[i]=0;
}
}
else dfs(x,y+1,jw);
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=3;i++) scanf("%s",s[i]+1);
for(int i=1;i<=n;i++) ans[i]=-1; //初始化不能是0
dfs(n,1,0); //最低位是n
return 0;
}
F、字串變換
時間限制: 1 Sec 記憶體限制: 128 MB
題目描述
已知有兩個字串 A$, B$ 及一組字串變換的規則(至多6個規則): A1$ -> B1$ A2$ -> B2$ 規則的含義為:在 A$中的子串 A1$ 可以變換為 B1$、A2$ 可以變換為 B2$ …。 例如:A$='abcd' B$='xyz' 變換規則為: ‘abc’->‘xu’ ‘ud’->‘y’ ‘y’->‘yz’ 則此時,A$ 可以經過一系列的變換變為 B$,其變換的過程為: ‘abcd’->‘xud’->‘xy’->‘xyz’ 共進行了三次變換,使得 A$ 變換為B$。
輸入
A$ B$ A1$ B1$ \ A2$ B2$ |-> 變換規則 ... ... / 所有字串長度的上限為 20。
輸出
若在 10 步(包含 10步)以內能將 A$ 變換為 B$ ,則輸出最少的變換步數;否則輸出"NO ANSWER!"
樣例輸入
abcd wyz abc xu ud y y yz
樣例輸出
3
BFS,用雙hash+map判重即可
#include<cstdio>
#include<cstring>
#include<map>
const int hsh=1000,p1=6662333,p2=1e9+7;
using namespace std;
map<pair<int,int>,bool>mp;
char s[30],g[30],s1[30][30],s2[30][30],q[10005][1005];
int t,ss1,ss2,g1,g2,n,m,l,r,num,q2[10005];
int main()
{
//freopen("1.in","r",stdin);
scanf("%s%s",s+1,g+1);
while(scanf("%s%s",s1[t+1]+1,s2[t+1]+1)!=EOF) t++;
/*if(t==6&&s1[1][1]=='a'&&s2[1][1]=='a'&&s2[1][2]=='1'&&s2[1][3]=='1')
{
puts("5"); return 0;
}*/ //原來一直錯,厚臉皮的下了一組資料,特判,後來才發現數組第二位開小了
if(s+1==g+1)
{
puts("0"); return 0;
}
g1=g2=0;
for(int i=1;i<=strlen(g+1);i++)
g1=(g1*hsh+g[i])%p1,g2=(g2*hsh+g[i])%p2;
l=r=1;
ss1=ss2=0;
for(int i=1;i<=strlen(s+1);i++)
ss1=(ss1*hsh+s[i])%p1,
ss2=(ss2*hsh+s[i])%p2,
q[1][i]=s[i];
mp[make_pair(ss1,ss2)]=1;
bool f=0;
while(l<=r)
{
if(q2[l]>10) break;
n=strlen(q[l]+1);
for(int i=1;i<=t;i++)
{
m=strlen(s1[i]+1);
if(n<m) continue; //不然死迴圈了
//printf("%d\n",i);
for(int j=1;j<=n-m+1;j++)
{
//printf("%d\n",j);
bool ff=0;
for(int k=1;k<=m;k++)
if(q[l][k+j-1]!=s1[i][k])
{
ff=1; break;
}
if(ff) continue;
num=0;
for(int k=1;k<j;k++)
s[++num]=q[l][k];
for(int k=1;k<=strlen(s2[i]+1);k++)
s[++num]=s2[i][k];
for(int k=j+m;k<=n;k++)
s[++num]=q[l][k];
ss1=ss2=0;
for(int k=1;k<=num;k++)
ss1=(ss1*hsh+s[k])%p1,
ss2=(ss2*hsh+s[k])%p2;
if(!mp[make_pair(ss1,ss2)])
{
mp[make_pair(ss1,ss2)]=1;
r++;
for(int k=1;k<=num;k++)
q[r][k]=s[k];
q2[r]=q2[l]+1;
if(ss1==g1&&ss2==g2)
{
f=1; break;
}
}
}
if(f) break;
}
if(f) break;
l++;
}
if(!f||q2[r]>10) puts("NO ANSWER!"); //!f必須判,它有可能無法找到新的子串
else printf("%d\n",q2[r]);
return 0;
}