完全な順列関数と自作の順列

注意:私が2年間で書いた新しい本「アルゴリズムコンペティション」は、2022年2月に清華大学出版社に引き渡され、2022年7月に出版される予定です。
「アルゴリズムコンペティション」は「大要」であり、「基本-中級-上級」をカバーし、長さは約700ページです。いくつかの知識ポイントのドラフトがこのブログに公開されています。


ブルー   ブリッジカップの州大会が間もなく開催 されます。ここに役立つ基本的な知識のポイントがあります。

  アレンジメントは、コンピュータープログラミングで一般的に使用される基本的な手法であり、すべてのアルゴリズムの競合には、アレンジメントを使用するトピックがあります。
  順列(C / C ++グループ)を実現するには、次の2つの方法を習得する必要があります。
   (1)STLのnext_permutation()関数。すべての順列を出力する必要がある場合は、この関数を直接使用してください。
   (2)自作の順列関数。アレンジメントの一部のみを出力する必要がある場合は、現時点ではnext_permutation()関数を使用できず、独自のコードを作成する必要があります。

1、next_permutation()

   STLで「次の」完全な順列を見つけるための関数はnext_permutation()です。たとえば、3文字のシーケンス{a、b、c}の場合、next_permutation()は辞書式順序で6つの組み合わせ(abc、acb、bac、bca、cab、cba)を返すことができます。
   関数next_permutation()の定義には、次の2つの形式があります。

bool next_permutation (BidirectionalIterator first, BidirectionalIterator last);
bool next_permutation (BidirectionalIterator first, BidirectionalIterator last, Compare comp);

   戻り値:次の順列の組み合わせがない場合はfalseを返し、そうでない場合はtrueを返します。next_permutation()が実行されるたびに、新しい配置が元のスペースに配置されます。
  次の点に注意してください。
(1)next_permutation()順列の範囲は、firstを含み、lastを除く[first、last)です。
(2)next_permutation()は、現在の完全な順列から開始し、すべての完全な順列を出力する代わりに、より大きな完全な順列を1つずつ出力します。たとえば、最初のシーケンスは{b、c、a}ですが、これは辞書式順序で最小ではなく、現時点で出力できるのは3つだけです。

#include <bits/stdc++.h>
using namespace std;
int main(){
    
    
    string s="bca";
    do{
    
     
       cout<<s<<endl;
    }while(next_permutation(s.begin(),s.end()));  //end()指向最后一个字符的下一个位置
    return 0;
}    

   コードは、6つではなく、3つの完全な順列のみを出力できます。

bca
cab
cba

(3)すべての完全な順列を取得したい場合は、最小の完全な順列から始める必要があります。最初の完全な順列が最小でない場合は、最初にsort()を使用して並べ替え、最小の順列を取得してから、next_permutation()を実行します。例えば:

#include <bits/stdc++.h>
using namespace std;
int main(){
    
    
    string s="bca"; 
    sort(s.begin(),s.end());  //字符串内部排序,得到最小的排列“abc”
    do{
    
     
       cout<<s<<endl;
    }while(next_permutation(s.begin(),s.end()));
    return 0;
}   

この時点で、すべての完全な順列、合計6つを   出力できます。

abc
acb
bac
bca
cab
cba

(4)シーケンス内に要素が繰り返されている場合、next_permutation()によって生成された順列は重複排除されます。たとえば、「aab」の場合、コードは6つの順列ではなく、3つの順列{aab、aba、baa}を出力します。

#include <bits/stdc++.h>
using namespace std;
int main(){
    
    
    string s="aab"; 
    sort(s.begin(),s.end());  //字符串内部排序,得到最小的排列“abc”
    do{
    
     
        cout<<s<<endl;
    }while(next_permutation(s.begin(),s.end()));
    return 0;
}   

   出力3の順列:

aab
aba
baa

   STLには完全な順列関数prev_permutation()もあります。これは、next_permutation()の反対である「前の」順列の組み合わせ、つまり「大きいものから小さいものへ」を探します。
   next_permutation()は非常に便利ですが、n個の数値からm個の部分置換を出力できない場合があります。場合によっては、置換プロセス中に処理する必要があります。この場合、置換関数を自分で作成する必要があります。

2.2。自作順列関数

   以下は、部分置換を実現できる自作の完全置換関数です。完全順列関数を再帰的に記述し、b []を使用して新しい完全順列を記録します。bfs()に初めて入るとき、b [0]はn個の数値の1つを選択し、2回目にbfs()に入るとき、 b [1]残りのn-1個の数字の1つ、...などを選択します。vis []を使用して、特定の番号が選択されているかどうかを記録します。選択した番号を後で選択することはできません。
   コードは、a []の数字が小さいものから大きいものまでの場合、小さいものから大きいものまですべての配置を出力できます。最初にa[]を並べ替えることができます。

#include<bits/stdc++.h>
using namespace std;
int a[20] = {
    
    1,2,3,4,5,6,7,8,9,10,11,12,13};
bool vis[20];         //记录第i个数是否用过
int b[20];            //生成的一个全排列
void dfs(int s,int t){
    
    
    if(s == t) {
    
          //递归结束,产生一个全排列
        for(int i = 0; i < t; ++i)  
            cout << b[i] << " ";    //输出一个排列            
        cout<<endl;
        return;
    }
    for(int i=0;i<t;i++)
        if(!vis[i]){
    
    
            vis[i] = true;   
            b[s] = a[i];
            dfs(s+1,t);
            vis[i] = false;
        }
}
int main(){
    
    
    int n = 3;
    dfs(0,n);     //前n个数的全排列
    return 0;
}

   出力:

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

たとえば、任意のm個のn個の数値の配置   を印刷する必要がある場合は、4個の数値から任意の3個の数値の配置を取得し、21行目をn = 4に変更してから、dfs()の7行目を変更してコードに従う:

#include<bits/stdc++.h>
using namespace std;
int a[20] = {
    
    1,2,3,4,5,6,7,8,9,10,11,12,13};
bool vis[20];        //记录第i个数是否用过
int b[20];           //生成的一个全排列
void dfs(int s,int t){
    
    
    if(s == 3) {
    
         //递归结束,取3个数产生一个排列
       for(int i = 0; i < 3; ++i)     //打印4个数中3个数的排列
            cout << b[i] << " ";
        cout<<endl;
        return;
    }
    for(int i=0;i<t;i++)
        if(!vis[i]){
    
    
            vis[i] = true;
            b[s] = a[i];
            dfs(s+1,t);
            vis[i] = false;
        }
}
int main(){
    
    
    int n = 4;
    dfs(0,n);     //前n个数的全排列
    return 0;
}

出力:

1 2 3
1 2 4
1 3 2
1 3 4
1 4 2
1 4 3
2 1 3
2 1 4
2 3 1
2 3 4
2 4 1
2 4 3
3 1 2
3 1 4
3 2 1
3 2 4
3 4 1
3 4 2
4 1 2
4 1 3
4 2 1
4 2 3
4 3 1
4 3 2

3.例

   以下は、自作の完全な順列を必要とし、next_permutation()を使用できない例です。


冬休みの宿題(2016年ブルーブリッジカップ州大会のC ++グループAの質問6)
タイトルの説明:足し算、引き算、掛け算、割り算の4つの操作:
□+□=
□□-□=
□□×□=□
□÷□=□
各四角は1から13までの数字を表しますが、繰り返すことはできません。
Q:オプションは全部でいくつありますか?


タイトルは13!完全順列の問題です。next_permutation()を使用すると、次のコードを簡単に記述できます。

#include <bits/stdc++.h>
using namespace std;
int a[20] = {
    
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
int main() {
    
    
  int ans=0;
  do{
    
     
     if(a[0]+a[1]==a[2] && a[3]-a[4]==a[5] &&a[6]*a[7]==a[8] && a[11]*a[10]==a[9])
          ans++;
  }while(next_permutation(a,a+13));
  cout<<ans<<endl;
}

   残念ながら、上記のコードは深刻にタイムアウトします。これは、コードを実行している13!= 6,227,020,800が、長時間終了できないためです。
   next_permutation()は毎回完全な順列を生成する必要があり、途中で停止することはできないため(たとえば、5つの数値の順列は最初の3つの順列のみを出力します)、この場合は次のようになります。使いにくい。
   タイトルの分析は、完全な配置を生成する必要が実際にはないことを示しています。たとえば、順列の最初の3つの数字が「□+□=□」を満たさない場合、次の9つの数字はどのように配置されていても間違っています。検索を早期に終了するこの手法は「プルーニング」と呼ばれ、プルーニングは検索における一般的な最適化手法です。次のコードは、自作の完全な配置に剪定を追加します。

#include<bits/stdc++.h>
using namespace std;
int a[20]={
    
    1,2,3,4,5,6,7,8,9,10,11,12,13};
bool vis[20]; 
int b[20];    
int ans=0;
void dfs(int s,int t){
    
    
    if(s==12) {
    
    
        if(b[9]*b[10] == b[11])   ans++;  
        return;
    }
    if(s==3 && b[0]+b[1]!=b[2]) return; //剪枝
    if(s==6 && b[3]-b[4]!=b[5]) return; //剪枝
    if(s==9 && b[6]*b[7]!=b[8]) return; //剪枝
    for(int i=0;i<t;i++)
        if(!vis[i]){
    
    
            vis[i]=true;
            b[s]=a[i];  //本题不用a[],改成b[s]=i+1也行
            dfs(s+1,t);
            vis[i]=false;
        }
}
int main(){
    
    
    int n=13;
    dfs(0,n); //前n个数的全排列
    cout<<ans;
    return 0;
}

   コードを実行すると、すぐに答えが出力されます。

64

4.演習

以下のアレンジの練習は、ブルーブリッジカップの公式ウェブサイトからのものです。
https://www.lanqiao.cn/problems/782/learning
https://www.lanqiao.cn/problems/572/learning
https://www.lanqiao.cn/problems/269/learning
https:// www .lanqiao.cn / problem / 208 / Learning

おすすめ

転載: blog.csdn.net/weixin_43914593/article/details/123652919