枚舉

舉出所有可能一個一個檢查

舉出所有可能一個一個檢查

但要聰明的枚舉,把時間壓到最小

範例

聰明的枚舉 : 找迴文

有一個長度為 n 的字串

請問其中有多少個子字串

有一個長度為 n 的字串

請問其中有多少個子字串

ex :
sdffdw

有一個長度為 n 的字串

請問其中有多少個子字串

ex :
sdffdw

 ans : 8個

比較笨的枚舉 :

窮舉所有L R  ( 1 <= L < R <=n )

 

比較笨的枚舉 :

窮舉所有L R  ( 1 <= L < R <=n )

 

對於一組L R,檢查是否為

True : as++

比較笨的枚舉 :

窮舉所有L R  ( 1 <= L < R <=n )

 

對於一組L R,檢查是否為

True : as++

O(n^2)

O(n)

O(n^3)

沒那麼笨的枚舉

沒那麼笨的枚舉

提示 : 利用的特性 

沒那麼笨的枚舉

提示 : 利用的特性 

枚舉回文中點 !!

沒那麼笨的枚舉

提示 : 利用回文的特性 

枚舉回文中點 !!

abcddcba

枚舉這個(中心)

枚舉中點 : O(n)

每次擴大一格,最多擴大n/2次 : O(n)

O(n^2)

改變枚舉的方法,就可以省非常多時間

開始枚舉各種東西

遞迴枚舉 : Lotto

給你k個數字,印出所有大小為6的子集合

k <= 13

ex:

input : 
2
7 1 2 3 4 5 6 7


ans : 
1 2 3 4 5 6
1 2 3 4 5 7
1 2 3 4 6 7
1 2 3 5 6 7
1 2 4 5 6 7
1 3 4 5 6 7
2 3 4 5 6 7

遞迴枚舉 !

遞迴枚舉 !

 

依序決定每個元素是否要拿(遞迴出兩個分支),並記錄當前拿的數量

#include<bits/stdc++.h>
using namespace std;

int T,k,a;
vector<int> p,ch;
void dfs(int N,int t){
    if(t>6) return;
    if(N==k){
        if(t<6) return;
        for(int i=0;i<ch.size();++i)cout<<ch[i]<<' ';
		cout<<endl;
        return;
    }
    ch.push_back(p[N]);
    dfs(N+1,t+1);
    ch.pop_back();
    dfs(N+1,t);
}
int main(){
    ios::sync_with_stdio(0); cin.tie(0);
    cin>>T;
    while(T--){
        cin>>k;
        p.clear();
        for(int i=0;i<k;++i){
            cin>>a;
            p.push_back(a);
        }
        dfs(0,0);
    }
    return 0;
}

遞迴枚舉 : 八皇后

8 Queens

有一個 8*8的棋盤,有幾種方法能放8個皇后,

並且任兩皇后不可互相攻擊,

請印出所有可能的盤面

8 Queens

有一個 8*8的棋盤,有幾種方法能放8個皇后,

並且任兩皇后不可互相攻擊,

請印出所有可能的盤面

方法一:列出所有可能,一一檢查

方法一(無優化) : 列出所有可能,一一檢查

每個格子有兩種可能

2^64 * 64

方法一:列出所有可能,一一檢查

每個格子有兩種可能

2^64  * 64

TLE

永遠算不完

(檢查時間)

優化一

每橫排最多只能放一個,

因此每舉每橫排Queen的位置,最後檢查

優化一

每橫排最多只能放一個,

因此每舉每橫排Queen的位置,最後檢查

8^8 * 64 少了很多,但還是TLE

優化二

既然我已知我上面的皇后位置 (由上排枚舉至下排),

那我不是可以不用枚舉上面已經用過的column

優化二

既然我已知我上面的皇后位置 (由上排枚舉至下排),

那我不是可以不用枚舉上面已經用過的column

ex : 

只有勾勾要枚舉

實作上可以用一個陣列紀錄那些column已經被用過

實作上可以用一個陣列紀錄那些column已經被用過

其實優化到這裡,已經可以快速跑完8皇后了 !!

實作上可以用一個陣列紀錄那些column已經被用過

其實優化到這裡,已經可以快速跑完8皇后了 !!

但這時就出現了 n 皇后問題

n <= 15

TLE

優化三

到目前為止,我們已經可以快速檢查column是否被占用,

但檢查斜排還是要花 O(n)時間

如何優化?

優化三

到目前為止,我們已經可以快速檢查column是否被占用,

但檢查斜排還是要花 O(n)時間

如何優化?

觀察看看斜排有什麼性質

只要是左上到右下的斜排,就是x-y相等(y-x也行)

右上到左下的斜排,就是x+y相等

因此也可以快速檢查斜排了 !!

因此也可以快速檢查斜排了 !!

實作可以用陣列,或set(比較慢)

code

(這裡用set,陣列留給你們當練習,記得處理負數)

#include<bits/stdc++.h>
using namespace std;
#define maxn 16

int as,y[maxn],n;
set<int> st1,st2;
void rec(int N){
    if(N==n+1){
        as++;
        return;
    }
    for(int i=1;i<=n;++i){
        if(y[i]) continue;
        if(st1.count(N-i)) continue;
        if(st2.count(N+i)) continue;
        y[i] = 1;
        st1.insert(N-i);
        st2.insert(N+i);
        rec(N+1);
        y[i] = 0;
        st1.erase(N-i);
        st2.erase(N+i);
    }
}
main(){
    cin>>n;
    rec(1);
    cout<<as<<endl;
}

有障礙的n 皇后問題

其實實作上幾乎一樣

枚舉所有排列

ex :

123 

123

132

231

213

312

321

這些排法

可以用遞迴的

就是窮舉每一個位置要放誰,做上記號

但c++有內建好用的工具

可以用遞迴的

就是窮舉每一個位置要放誰,做上記號

但c++有內建好用的工具

next_permutation();

可以用遞迴的

就是窮舉每一個位置要放誰,做上記號

但c++有內建好用的工具

next_permutation();

這也是 do while語法唯一的用處

next_permutation(begin,end);

會把目標列迭代成下一個字典序,若已是最大字典序則回傳 false,因此一開始要sort

#include<bits/stdc++.h>
using namespace std;
#define int long long

vector<int> arr;
int n;
main(){
    cin>>n;
    for(int i=1;i<=n;++i){
        int a; cin>>a;
        arr.push_back(a);
    }
    sort(arr.begin(),arr.end());

    do{
        for(int i=0;i<n;++i) cout<<arr[i]<<' ';
        cout<<endl;
    }while(next_permutation(arr.begin(),arr.end()));
}
Made with Slides.com