C++ アルゴリズムの基本はトピックを磨く必要があります - 貪欲

よく深い

  貪欲なアルゴリズム (貪欲なアルゴリズム、貪欲なアルゴリズムとも呼ばれる) は、問題を解決するときに、現時点で常に最良の選択を行うことを意味します。つまり、全体の最適性を考慮せずに、アルゴリズムはある意味で局所的な最適解を求めます。
  貪欲なアルゴリズムは、すべての問題に対して全体的な最適解を得るわけではなく、重要なのは貪欲な戦略の選択です

1. 数字のスペル

NC16783パズル

トピックの説明

n 個の正の整数 (n ≤ 20) を想定し、それらを連続して接続して最大の複数桁の整数を形成します。
例: n=3 の場合、3 つの整数 13、312、343 を接続して形成される最大の整数は: 34331213 別の例: n=4 の場合、4 つの整数 7、13、4、246 を接続して形成される最大の整数は:
7424613

説明を入力してください:

最初の行では、正の整数 n です。
2 行目に、n 個の正の整数。

出力の説明:

最大の整数を表す正の整数

例 1


3
13 312 343を入力してください

出力
34331213

問題解決のアイデア:
1. 貪欲に、最適解に従って文字列を並べ替えます。つまり、s1+s2>s2+s1 です。

コード:

#include<bits/stdc++.h>
using namespace std;
bool cmp(string s1,string s2){
    
    
    return s1+s2>s2+s1;//不能简单地s1>s2,因为202和20200,应该把202放前面
}
int main(){
    
    
    int n;
    string s[25];
    cin>>n;
    for(int i=0;i<n;i++){
    
    
        cin>>s[i];
    }
    sort(s,s+n,cmp);
    string ans;
    for(int i=0;i<n;i++){
    
    
        ans+=s[i];
    }
    cout<<ans<<endl;
}

2. 座席の列

NC16618座席列

トピックの説明

  授業中は常に生徒同士のささやき声があり、小学校の担任教師にとっては非常に悩ましい問題です。しかし、クラスの教師であるXiaoxueは、生徒の座席が決定された後、限られた数のDペアだけがクラスで互いにささやき合うという興味深い現象を発見しました。
生徒は教室の M 行 N 列に座っており、i 行 j 列に座っている生徒の位置は (i, j) で、生徒の出入りを容易にするために、K 個の水平通路が設定されています。教室では、L縦チャンネル。
そこで、賢いシャオシュエは、授業中の生徒のささやきの問題を減らす方法を考えました。彼女は、机と椅子を再配置し、生徒の机と椅子の間の通路の位置を変更することを計画しました。 2 人のクラスメートを引き離すと、お互いにささやきません。
Xiaoxue が最適なチャネル分割スキームを提供するプログラムを作成するのを手伝ってください。この方式では、クラスでささやく学生のペアの数が最も少なくなります。

説明を入力してください:

  最初の行には、スペースで区切られた 5 つの整数があり、M、N、K、L、D (2 ≤ N、M ≤ 1000、0 ≤ K < M、0 ≤ L < N、D ≤ 2000) M です。 、N、K、L、D (2≤N、M≤1000、0≤K<M、0≤L<N、D≤2000)。次のD行には、各行スペース区切らた4 つ
  整数が含まれますi ) は互いにささやきます (入力は、それらが互いに隣接しているか、互いに隣接していることを保証します)。入力データは、最適解の一意性を保証します。

出力の説明:

  全部で 2 行あり、
  最初の行には K 個の整数 a 1 a 2 ... a Kが含まれており、これは行 a 1と行 a 1 +1、行 a 2と行a 2 +1 の間、... を意味します。 ,チャネルは行 a Kと行 a K +1 の間に開く必要があります。ここで、a i < a i +1 で、2 つの整数ごとにスペースで区切ります (行末にスペースはありません)。
  2 行目には L 個の整数 b 1 b 2 ... b Kが含まれています。これは、行 b 1行b 1 +1 の間、行 b 2と行 b 2 +1の間、...、行 b K A チャネルを意味します。は行と b K +1 行 (b i < b i +1) の間で開く必要があり、2 つの整数はすべてスペースで区切られます (行末にスペースはありません)。

例 1

入力
4 5 1 2
3
4 2 4 3 2 3
3 2 5 2 4

出力
2
2 4

例証する

ここに画像の説明を挿入
上の図ではささやきを行う3組の生徒の位置を*、※、+の記号で示しており、図中の3本の太い線の位置がチャンネルを表しています。唯一の最適なものです。

問題を解決するためのアイデア:
1. まず、各行と列でささやきをしている生徒のペアの数を数え、対数の降順で並べ替えます。

2. KとLから最適な行と列をとり、昇順にソートして出力する

コード:

#include<bits/stdc++.h>
using namespace std;
int main(){
    
    
    int n,m,k,l,d;
    int a[1010]={
    
    0};
    int b[1010]={
    
    0};
    vector<vector<int>> v1,v2;
    cin>>n>>m>>k>>l>>d;
    for(int i=0;i<d;i++){
    
    
        int x,y,p,q;
        cin>>x>>y>>p>>q;
        if(x==p){
    
    
            b[min(y,q)]++;//列
        }else{
    
    
            a[min(x,p)]++;//行
        }
    }
    for(int i=1;i<1000;i++){
    
    
        v1.push_back({
    
    a[i],i});//行
        v2.push_back({
    
    b[i],i});//列
    }
    sort(v1.rbegin(),v1.rend());//从大到小排序
    sort(v2.rbegin(),v2.rend());//从大到小排序
    vector<int> v3,v4;
    for(int i=0;i<k;i++){
    
    
        v3.push_back(v1[i][1]);
    }
    for(int i=0;i<l;i++){
    
    
        v4.push_back(v2[i][1]);
    }
    sort(v3.begin(),v3.end());//答案从小到大排序
    sort(v4.begin(),v4.end());//答案从小到大排序
    for(int c:v3){
    
    
        cout<<c<<' ';
    }
    cout<<endl;
    for(int c:v4){
    
    
        cout<<c<<' ';
    }
}

3. マトリックス消去ゲーム

NC200190行列消しゲーム

トピックの説明

Niumi は行列消去と呼ばれるゲームをプレイしています. 行列のサイズは n 行 m 列です. 行 i 列 j のセルの重みは a i,j です. Niumi は kラウンド  のゲームをプレイできます . In eachラウンドでは、Niumi は行または列を選択してから、この行または列のすべてのセルの重みを 0 に変更できます。Niumi のスコアは、この行または列のすべてのセルに追加されます。
  Niumei はスコアを最大化したいと考えています。Qiuqiu、彼女を助けてください!

説明を入力してください:

最初の行の3 つの整数 n、m、k
は、行列内の各セルの重みを表します。

出力の説明:

Niumei が取得できる最大スコアを表す整数を出力します。

例 1

入力
3 3 2
101 1
102 1 202 1
100 8 100

出力
414

問題を解決するためのアイデア: 1. 暴力的な列挙によって行を削除する方法は
最大で 215 あります。次に、貪欲な方法を使用して列を削除し、最適な行を削除します。

2. すべての除去方法を 2 進数で列挙できます。1 を使用して除去する行を表します。たとえば、00101 は 3 番目と 5 番目の行の除去を表し、すべてを 0~(1<<n ) 排除計画

コード:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int cnt(int val){
    
    //计算有多少个数字val二进制有多少个1
    int ans=0;
    while(val){
    
    
        ans++;
        val&=(val-1);
    }
    return ans;
}
signed main(){
    
    
    int n,m,k;
    int a[20][20]={
    
    0};
    cin>>n>>m>>k;
    for(int i=0;i<n;i++){
    
    
        for(int j=0;j<m;j++){
    
    
            cin>>a[i][j];
        }
    }
    int ans=0;
    for(int i=0;i<(1<<n);i++){
    
    //(1<<n)种行消除方式
        int t=k-cnt(i);
        int cu[20]={
    
    0};
        if(t<0)continue;
        int sum=0;
        for(int j=0;j<n;j++){
    
    
            if((i>>j)&1){
    
    //如果这位是1,那么该行就消除
                for(int k=0;k<m;k++){
    
    
                    sum+=a[j][k];
                }
            }else{
    
    //如果是0,那么就加在每一列里
                for(int k=0;k<m;k++){
    
    
                    cu[k]+=a[j][k];
                }
            }
        }
        sort(cu,cu+m);//排序后取最优的t列
        for(int j=m-1;j>m-1-t&&j>=0;j--){
    
    
            sum+=cu[j];
        }
        ans=max(ans,sum);//取最优解
    }
    
    cout<<ans<<endl;
}

4. ユエユエの歌を聞く華華

NC23036ユエユエの歌を聞く華華

トピックの説明

  ユエユエの歌が上手い!Huahua は、Yueyue が歌った曲を特定の Web サイトでリリースしたと聞いて、完全な曲を USB フラッシュ ドライブにダウンロードしました。しかし、Huahua が誤って USB フラッシュ ドライブを落としてしまい、内部のファイルが破損してしまいました。月越の歌は、1からNまでの正の整数を順番に並べたものとみなすことができ、現在ではいくつかの区間となっており、これらの区間が重なり合っている可能性があります。Huahua はそれを完全な曲に復元したいと考えています。つまり、和集合が 1 から N を含むようにいくつかのフラグメントを見つけたいと考えています (この質問では整数のみに焦点を当てていることに注意してください。例 1 を参照してください)。しかし、Huahua は怠け者なので、最小の間隔を選択したいと考えています。Huahua が少なくともいくつの間隔を選択するかを計算してください。Huahua の U ディスクがひどく損傷しているため、できない場合があります。できない場合は、-1 を出力してください。

説明を入力してください:

最初の行の 2 つの正の整数 N と M は、曲の元の長さとセグメントの数を表します。
次の M 行では、各行の 2 つの正の整数 L と R は、i 番目のセグメントに対応する区間が [L, R] であることを示しています。

出力の説明:

実行できる場合は、必要なフラグメントの最小数を出力し、そうでない場合は -1 を出力します。

例 1


4 2
1 2
3 4を入力してください

出力
2

例 2


4 2
1 1
3 4を入力してください

出力
-1

例 3


10 5
1 1
2 5
3 6
4 9
8 10を入力してください

出力
4

注:
1≦L≦R≦ 10 9、1≦N≦10 9、1≦M≦10 5

問題解決のアイデア:
貪欲な思考、選択できるフラグメントの中から、毎回最適なフラグメントを選択します。つまり、左の境界が接続できることを確認し、右の境界を可能な限り拡張します。

コード:

#include<bits/stdc++.h>
using namespace std;
int main(){
    
    
    int n,m;
    cin>>n>>m;
    vector<vector<int>> v;
    for(int i=0;i<m;i++){
    
    
        int a,b;
        cin>>a>>b;
        v.push_back({
    
    a,b});
    }
    sort(v.begin(),v.end());//区间按照升序排序
    int t=0;
    int g=0;
    int ans=0;
    int flag=1;
    for(int i=0;i<v.size();i++){
    
    
        if(v[i][0]<=t+1){
    
    //尽可能地往右扩充
            g=max(g,v[i][1]);
        }else{
    
    //选择最优的片段
            ans++;
            t=g;
            g=v[i][1];
            if(v[i][0]>t+1){
    
    //无法选择这个区间
                flag=0;
                break;
            }
        }
        if(g>=n){
    
    //提前退出
            ans++;
            break;
        }
    }
    if(flag&&g>=n){
    
    
        cout<<ans<<endl;
    }else{
    
    
        cout<<-1<<endl;
    }
}

とてもシンプルですか?

新参者には間違いなく難しいでしょう. より多くの質問をして、より多く使用してください. 一度それに慣れれば、それは簡単になります. 孟兄弟、さあ! ! !

記事がまだ不十分です、修正していただきありがとうございます

見てくれてありがとう、いいね

Guess you like

Origin blog.csdn.net/weixin_52115456/article/details/128482761