暴力列舉演算法的優化 抽籤問題

2021-07-09 13:41:26 字數 2526 閱讀 1119

將寫有數字的n個紙片放入口袋中,你可以一次從口袋抽取4次紙片,每次記下紙片的數字後將其放回口袋。如果這4個數字的和是m,那麼你就贏了,否則你就輸了。編寫程式,判斷當紙片上的數字是k1,k2,…,kn時,是否存在抽取4次和為m的方案。如果存在,輸出yes;否則輸出no.

限制條件,資料規模

1<=n<=1000,1<=m<=10^8,1<=ki

<=10^8.

時間限制為1s.

輸入的第一行表示n,第二行表示m,第三行的n個數字表示k.

1 3 5

1 3 5

yesno

這屬於很經典的列舉問題。剛一拿到這一題目的想法就是用4個for迴圈暴力列舉所有可能的方案,再判斷是否存在k[a]+k[b]+k[c]+k[d]的和為m,存在就輸出yes,否則輸出no.這是很直觀也很自然的思路,演算法的時間複雜度是o(n4).

#includeusing namespace std;

const int max_n=1000;

int main()

{ freopen("in.txt","r",stdin);

int n,m,k[max_n];

while(cin>>n>>m)

{ for(int i=0;i>k[i];

bool flag=false;

for(int a=0;a上面的**通過測試用例沒有問題。但是,只要一提交肯定要超時,因為時間複雜度為n^4,當n取1000的時候,n^4=10^12,超時是一定的,所以需要改進演算法。其實演算法最核心的地方就是那4個for迴圈。最內層的for迴圈所做的事情就是檢測在k[n]中是否存在這樣乙個d,使得

ka+kb+kc+kd=m

其實這個可以換一種方式表達,

kd=m-ka-kb-kc.

也就是說,檢查陣列k中的所有元素,判斷是否有m-ka-kb-kc.其實在乙個排序的陣列中檢查有個很快的檢查方法就是二分搜尋。所以優化的思路就來了,可以考慮將k先進行一次排序,然後看k最中間的數值:

假設m-ka-kb-kc的值為x,

如果比x小,x只可能在它的後半段;

如果比x大,x只可能在它的前半段。

再這樣遞迴地進行搜尋,最終就可以確定x是否存在。弱存在則輸出yes,否則輸出no。

二分搜尋演算法每次將候選區間縮小至原來的一半,所以演算法的時間複雜度就是排序的時間和迴圈的時間。其中,排序時間為o(nlogn),迴圈的時間為o(n^3logn),所以最終的時間複雜度為o(n^3logn)。演算法實現如下:

#include#includeusing namespace std;

const int max_n=1000;

int n,m,k[max_n];

bool binary_search(int x)

{ //搜尋範圍是k[l],k[l+1],...,k[r-1]

int l=0,r=n;

while(r-l>=1)

{ int mid=(l+r)/2;

if(k[mid]==x)

return true;

else if(k[mid]>n>>m)

{ for(int i=0;i>k[i];

sort(k,k+n);

bool flag=false;

for(int a=0;a演算法的時間複雜度是o(n^3logn),當n=1000的時候,還是無法滿足時間要求,所以演算法還需要進一步優化。剛剛關注的是最內層的迴圈,其實可以目光看得開一點,關注內層的兩個迴圈,內層的兩個迴圈所做的工作就是檢查在陣列k中是否存在c和d,使得kc+kd=m-ka-kb.

在這種情況下並不能直接使用二分搜尋。但是,如果事先列舉出kc+kd所得的n^2個數值後再排序就可以利用二分搜尋直接進行查表就可以了。其實,更加準確地說,kc+kd所得的數字去除重複後只有n*(n+1)/2個。

其中排序時間是o(n^2logn),查詢時間是o(n^2logn),所以演算法的時間複雜度就是o(n^2logn),所以當n=1000時也可以滿足要求。ac**如下:

#include#includeusing namespace std;

const int max_n=1000;

int n,m,k[max_n],temp[max_n*max_n];

bool binary_search(int x)

{ //搜尋範圍是k[l],k[l+1],...,k[r-1]

int l=0,r=n*n;

while(r-l>=1)

{ int mid=(l+r)/2;

if(temp[mid]==x)

return true;

else if(temp[mid]>n>>m)

{ for(int i=0;i>k[i];

//列舉kc+kd

for(int c=0;c這題雖然不難,但是思路很值得借鑑,從乙個複雜度高的演算法出發,逐步優化,不斷降低演算法的複雜度直到滿足要求,這種方式很奏效。

抽籤問題(不斷優化)

問題描述 假設存在n個籤,通過抽取四次,如果四次抽取的和為m即勝利。初始解 1 for int a 0 a n a 212 13 14 難度增加,將n的限制條件為1 1000,此時,四重迴圈明顯複雜度過高,需改進演算法。上面所記載的程式的迴圈部分。最內側關於d的迴圈所做的事就是 檢查是否有d使得 通...

暴力演算法 簡單列舉

暴力求解法 知識點 要求設計者找出所有可能的方法,然後選擇其中的一種方法,若該方法不可行則試探下一種可能的方法。使用暴力法的幾種情況 1.搜尋所有的解空間 2.搜尋所有的路徑 3.直接計算 4.模擬和 例項 如下 long long儲存。如下 include int main count int m...

暴力列舉演算法篇

最簡單的暴力排列 public static void main string args 變化為遞迴型 記住3個步驟就記住了回溯演算法 1 遞迴最後一步 from to 即是組合完成形態 題型組合後的條件判斷 2 嘗試交換形態 嘗試遞迴 3 還原形態 進行下次嘗試 public static voi...