caioj1048: 寬搜3(巧妙取量)
阿新 • • 發佈:2018-11-21
1048: 寬搜3(巧妙取量)題目在此
時間限制: 1 Sec 記憶體限制: 128 MB
題目描述
【題目描述】
有三個容器,容量分別為 a,b,c(a> b > c ),一開始a裝滿油,現在問是否只靠abc三個容器量出k升油。
如果能就輸出“yes”,並且說明最少倒幾次,否則輸出“no”。
例如:10升油在10升的容器中,另有兩個7升和3升的空容器,要求用這三個容器倒油,
使得最後在abc三個容器中有一個剛好存有5升油,問最少的倒油次數是多少?
(每次倒油,A容器倒到B容器,或者A內的油倒完,或者B容器倒滿。 )
10 7 3
(10 0 0)
(3 7 0):第一次
(3 4 3):第二次
(6 4 0):第三次
(6 1 3):第四次
(9 1 0):第五次
(9 0 1):第六次
(2 7 1):第七次
(2 5 3):第八次,出現5了。
【輸入格式】
輸入有多組測試資料。
輸入a,b,c, k四個正整數( 100 a > b > c > = 1 , 1 < = k < 100 )
【輸出格式】
如果能得到k就輸出兩行
第一行“yes”,第二行為最少的次數,否則輸出“no”
【樣例輸入】
10 7 3 5
【樣例輸出】
yes
8
思路:首先的話,我們看到了這道題之後,第一感覺一定是搜尋對吧,而且因為是要一直判斷的,所以我們可以使用寬搜,這個都是可以理解的吧。然後這道題目,其實在一定意義上面就是我們小學做過的一種奧數題的型別,真是扎心,接下來,按照老規矩,要審題,這道題目需要我們輸出兩個,同時是多組資料,這個是要注意的,然後看樣例得到的過程,不難看出,這個就是一個換罐子,把多的移到少的, 一直湊啊湊啊的過程,那麼這個思路就出來了,寬搜+多次判斷,
這個判斷有六種情況我在這裡列舉一下吧
用6個判斷,來判斷x,y,z兩兩之間的最小值,進行換罐子
6種情況如下:
1.my-tno.y,tno.x 2.mz-tno.z,tno.x 3.mx-tno.x,tno.y
4.mz-tno.z,tno.y 5.mx-tno.x,tno.z 6.my-tno.y,tno.z
到這裡都能夠理解的默默舉個爪子唄,很好(我是不是有點自作多情了,沒關係繼續),這道題的資料說真的還真不小,反正我定了1110000,然後就編目錄,通俗講就是定義結構體,而且有一個要注意的就是,因為要判斷是否可以輸出答案,就要用到一個 bool bk來判斷,如果等於true,說明有答案可以輸出,如果等於false,說明沒有答案輸出,總體來講大概就是這樣子吧,讓我再瞄一眼程式碼啊,哦哦哦哦哦有個
大大大重點:那就是我們在把原來的資料儲存在結構體裡面的時候,要把三個數結合起來儲存,這樣就可以只用一個數組來儲存,否則就要用三個,這樣就會大大大大大的浪費空間和時間,
什麼意思呢?就是比如說: a=55 , b=76 , c=33,那麼如果我們用常人的思維,是不是用三個陣列來儲存再帶人結構體裡面,這樣不說都知道會浪費很多的空間,最駭人的是這樣要判斷多次,很容易出錯。所以我們就要把 a*10000+b*100+c=55*10000+76*100+33=557633,那麼這樣就是在不影響他本身的情況下,用一個數組來儲存,就可以使得程式碼更加簡潔,同時判斷起來也更加方便,bool陣列的判斷也只用一個就okk了。
大致的思路就是這樣,剩下的看具體程式碼的實現吧,程式碼也寫得非常清楚了,不懂的就就就再看幾遍吧
/*
這種做法就是一直判斷,就是把每一種情況都列出來,找出最早成立的那一個
還有一個就是,因為是一種一種情況來判斷的
所以用佇列來處理也是可以的
然後我們要用6個判斷,來判斷x,y,z兩兩之間的最小值,進行換罐子
6種情況如下:
1.my-tno.y,tno.x 2.mz-tno.z,tno.x 3.mx-tno.x,tno.y
4.mz-tno.z,tno.y 5.mx-tno.x,tno.z 6.my-tno.y,tno.z
*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct node
{
int x,y,z,s;//x,y,z表示的就是題目中的a,b,c;s表示的是步數
}list[1110000];
bool v[1110000];//判斷重複的
int kk,head,tail,mx,my,mz;//head和tail不解釋
inline int ys(node tno) {return tno.x*10000+tno.y*100+tno.z;}
/*
這是節省空間的一步,就比如說:22 55 77
那麼經過了ys這一步之後就變成了 225577
把三個數拼在一起變成一個數來判斷
如果不用這一步的話,就要分三個來儲存自然就會浪費空間
*/
inline int min(int x,int y) {return x<y?x:y;}
inline int max(int x,int y) {return x>y?x:y;}
int main()
{
while(scanf("%d%d%d%d",&mx,&my,&mz,&kk)!=EOF)//多組資料
{
list[1].x=mx; list[1].s=list[1].y=list[1].z=0;
/*
這一步就是初始化,就是在最開始把所有的油都放在最大的罐子裡
那麼其他的東西都為0,因為油都放在最大的罐子mx裡
my和mz就是空的,步數也是空的
*/
head=1; tail=2; memset(v,false,sizeof(v));
/*
這個理解成初始化,也就是說,把這個過程變成了一個委屈巴巴的佇列,
head=1,tail=2是為了方便,這樣就能清楚看出當前的這個佇列是有人的
*/
v[ys(list[1])]=true;//第一種情況
bool bk=false;//bk是用來判斷輸出的
while(head<tail)//這樣就表示當前這個佇列有人
{
node tno=list[head]; int k;
//定義tno是為了不打那麼多,防止出錯
//k是用來找最小的來換罐子,按照做法可以這麼理解
if(tno.x==kk || tno.y==kk || tno.z==kk)//如果有一個滿足題目的要求
{
printf("yes\n%d\n",tno.s);//就輸出yes並輸出步數(tno.s代表經過的步數)
bk=true; break;//退出
}
k=min(my-tno.y,tno.x);
/*
如果沒有滿足題目的條件,就進行換罐子,
用最小值來給油換罐子(第二個罐子的總存量-第二個罐子當前的量和第一個罐子的量做比較)
(這一步是尋找要換的罐子)
*/
tno.x-=k; tno.y+=k;//這個表示的就是換罐子的過程
if(!v[ys(tno)])
/*
這一步就是判斷重複的過程,ys上面解釋過了,
然後在上面的第一次換罐子的時候我們將第一種情況記錄下來了,
也就是說,只要當前的情況和成立過的情況不一樣就是OK的
*/
{
v[ys(tno)]=true; tno.s++;
/*
進入了迴圈之後,說明我們找到了一種新的情況,
這個時候就要用v陣列把這種情況儲存起來,用來後面判斷,
然後這個時候,我們是進行了下一輪的換罐子,
所以tno.s++,就是在步數那裡增加一種情況
*/
if(tno.x==kk || tno.y==kk || tno.z==kk)
{
printf("yes\n%d\n",tno.s);
bk=true; break;
}//同樣的判斷,如果成立就輸出,不成立就退出,進行下一步的操作
list[tail]=tno; tail++;
/*
這一步就是把當前這一種情況的值儲存在list這個結構體裡面,
解釋一下:list[head]表示的是第一種情況,然後list[tail]就是用來儲存每一種情況的狀態,
然後我們在進行下一步之前,要把tno的狀態變回list[head]的狀態,
這樣才可以保證不會有情況漏掉,也可以保證每一種情況的6種狀態都可以經歷一次
*/
}
tno=list[head];//恢復初始狀態
k=min(mz-tno.z,tno.x);//尋找要換的罐子
tno.x-=k; tno.z+=k;//換罐子
if(!v[ys(tno)])
{
v[ys(tno)]=true; tno.s++;
if(tno.x==kk || tno.y==kk || tno.z==kk)
{
printf("yes\n%d\n",tno.s);
bk=true; break;
}
list[tail]=tno; tail++;
}
tno=list[head];
k=min(mx-tno.x,tno.y);
tno.y-=k; tno.x+=k;//和之前的一樣
if(!v[ys(tno)])
{
v[ys(tno)]=true; tno.s++;
if(tno.x==kk || tno.y==kk || tno.z==kk)
{
printf("yes\n%d\n",tno.s);
bk=true; break;
}
list[tail]=tno; tail++;
}
tno=list[head];
k=min(mz-tno.z,tno.y);
tno.y-=k; tno.z+=k;//和之前的一樣
if(!v[ys(tno)])
{
v[ys(tno)]=true; tno.s++;
if(tno.x==kk || tno.y==kk || tno.z==kk)
{
printf("yes\n%d\n",tno.s);
bk=true; break;
}
list[tail]=tno;tail++;
}
tno=list[head];
k=min(mx-tno.x,tno.z);
tno.z-=k; tno.x+=k;//和之前的一樣
if(!v[ys(tno)])
{
v[ys(tno)]=true; tno.s++;
if(tno.x==kk || tno.y==kk || tno.z==kk)
{
printf("yes\n%d\n",tno.s);
bk=true; break;
}
list[tail]=tno; tail++;
}
tno=list[head];
k=min(my-tno.y,tno.z);
tno.z-=k; tno.y+=k;//和之前的一樣
if(!v[ys(tno)])
{
v[ys(tno)]=true; tno.s++;
if(tno.x==kk || tno.y==kk || tno.z==kk)
{
printf("yes\n%d\n",tno.s);
bk=true; break;
}
list[tail]=tno; tail++;//和之前的一樣
}
head++;
/*
但是這個時候我們就已經把head=1的時候的狀態的6種情況都跑了一遍,
這個時候就是跑第二種情況要面臨的6種狀態,接著第三種一直到可以與題目相同為止
*/
}
if(bk==false) printf("no\n");
/*
如果bk在跑完了所以之後,仍然等於false,
說明沒有可以成立的狀態,那麼這麼時候就輸出no
*/
}
return 0;
}
簡直就是大大大大的okk,要是有大佬來臨的話我想放一個蒟蒻不懂的程式碼,望大佬點評。
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
struct node
{
int a,b,c,step;
}s,e;
int a,b,c,k;
bool f[105][105][105];
int bfs(node x)
{
queue<node> q;
q.push(x);
f[x.a][x.b][x.c]=true;
while(!q.empty())
{
node cur=q.front();
q.pop();
if(cur.a==k||cur.b==k||cur.c==k)
{
return cur.step;
}
if(cur.a>0&&cur.b<b)
{
int t=min(cur.a,b-cur.b);
node temp;
temp.a=cur.a-t;
temp.b=cur.b+t;
temp.c=cur.c;
temp.step=cur.step+1;
if(!f[temp.a][temp.b][temp.c])
{
q.push(temp);
f[temp.a][temp.b][temp.c]=1;
}
}
if(cur.a>0&&cur.c<c){
int t=min(cur.a,c-cur.c);
node temp;
temp.a=cur.a-t;
temp.b=cur.b;
temp.c=cur.c+t;
temp.step=cur.step+1;
if(!f[temp.a][temp.b][temp.c])
{
q.push(temp);
f[temp.a][temp.b][temp.c]=1;
}
}
if(cur.b>0&&cur.c<c)
{
int t=min(cur.b,c-cur.c);
node temp;
temp.a=cur.a;
temp.b=cur.b-t;
temp.c=cur.c+t;
temp.step=cur.step+1;
if(!f[temp.a][temp.b][temp.c])
{
q.push(temp);
f[temp.a][temp.b][temp.c]=1;
}
}
if(cur.b>0&&cur.a<a)
{
int t=min(cur.b,a-cur.a);
node temp;
temp.a=cur.a+t;
temp.b=cur.b-t;
temp.c=cur.c;
temp.step=cur.step+1;
if(!f[temp.a][temp.b][temp.c])
{
q.push(temp);
f[temp.a][temp.b][temp.c]=1;
}
}
if(cur.c>0&&cur.a<a)
{
int t=min(cur.c,a-cur.a);
node temp;
temp.a=cur.a+t;
temp.b=cur.b;
temp.c=cur.c-t;
temp.step=cur.step+1;
if(!f[temp.a][temp.b][temp.c])
{
q.push(temp);
f[temp.a][temp.b][temp.c]=1;
}
}
if(cur.c>0&&cur.b<b)
{
int t=min(cur.c,b-cur.b);
node temp;
temp.a=cur.a;
temp.b=cur.b+t;
temp.c=cur.c-t;
temp.step=cur.step+1;
if(!f[temp.a][temp.b][temp.c])
{
q.push(temp);
f[temp.a][temp.b][temp.c]=1;
}
}
}
return -1;
}
int main()
{
while(scanf("%d%d%d%d",&a,&b,&c,&k)!=EOF)
{
s.a=a,s.b=0,s.c=0,s.step=0;
memset(f,0,sizeof(f));
int ans=bfs(s);
if(ans==-1) printf("no\n");
else
{
printf("yes\n%d\n",ans);
}
}
return 0;
}
我大致也看了一下,發現其實和我原來的程式碼本質是一樣的,也是判斷那六種情況:
用6個判斷,來判斷x,y,z兩兩之間的最小值,進行換罐子
6種情況如下:
1.my-tno.y,tno.x 2.mz-tno.z,tno.x 3.mx-tno.x,tno.y
4.mz-tno.z,tno.y 5.mx-tno.x,tno.z 6.my-tno.y,tno.z