LoginSignup
1
1

More than 5 years have passed since last update.

品物の並び替え

Last updated at Posted at 2019-03-11

自分の解法(340ms)

  • 並び方を全通り試すのが$O(N!)$
  • 品物のスコアを判定するのが$O(NM)$
  • なので全体の計算量は$O(N!NM)$となる。
  • $N=9, M=72$が最大なので当てはめると計算ステップは$235146240$回となる。
  • 大体$10^8$回くらいだからギリギリだと思ったけど332msで解けてしまった。$10^8$回のステップが1秒ってのはあてにしすぎない方がいいのかも。

コード

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

#define int long long
template<class T>bool chmax(T &a, const T &b) { if (a<b) { a = b; return 1; } return 0; }

int N, M;
int item1[100];
int item2[100];
int score[100];

signed main() {
    cin >> N >> M;
    for (int i = 0; i < M; i++) {
        cin >> item1[i] >> item2[i] >> score[i];
    }

    // 並び方を定義する配列
    vector<int> v(N);
    iota(v.begin(), v.end(), 0);

    int ans = 0;
    do {
        int sum = 0; // この並びの時の合計スコア

        // その並びになってるかを全て調べる
        for (int i = 0; i < M; i++) {
            int cnt = 0;
            for (int j = 0; j < N; j++) {
                if (cnt == 0 and v[j] == item1[i]) {
                    cnt++;
                }
                if (cnt == 1 and v[j] == item2[i]) {
                    cnt++;
                }
            }
            // その並びになってたらスコアを足す
            if (cnt == 2) {
                sum += score[i];
            }
        }

        chmax(ans, sum);
    } while (next_permutation(v.begin(), v.end()));

    cout << ans << endl;

    return 0;
}

ちょっと改善する(17ms)

  • 2次元配列で持てば判定部分が$O(N^2)$になる
  • よって全体的な計算量は$O(N!N^2)$となる
  • N=9が最大なので最大の計算ステップは$29393280$回となる。大体$10^7$回なので余裕で間に合う
  • これ頭いい。普通に思いつかなくないか?

コード

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

#define int long long

template<class T>bool chmax(T &a, const T &b) { if (a<b) { a = b; return 1; } return 0; }

int N, M;
int item[100][100]; // item1, item2, scoreをこれひとつにまとめた

signed main() {
    cin >> N >> M;
    for (int i = 0; i < M; i++) {
        int a, b, c;
        cin >> a >> b >> c;
        item[a][b] = c;
    }

    vector<int> v(N);
    iota(v.begin(), v.end(), 0);

    int ans = 0;
    do {
        int sum = 0;

        for (int i = 0; i < N; i++) {
            for (int j = i+1; j < N; j++) {
                sum += item[v[i]][v[j]];
            }
        }

        chmax(ans, sum);
    } while (next_permutation(v.begin(), v.end()));

    cout << ans << endl;

  return 0;
}

DPを書いてみる(5ms)

  • とりあえず再帰で書いてみる。普通の再帰じゃなくてbitDPに寄せた再帰。コードはこんな感じ
  • 再帰がかけたらあとはメモ化するだけ
  • 計算量はよくわからない。けど早い
  • ループでDPを回すのはこんな感じのコード

コード

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

#define int long long

template<class T>bool chmax(T &a, const T &b) { if (a<b) { a = b; return 1; } return 0; }

int N, M;
int item[100][100];
int dp[1<<9];

int rec(int mask) {
    if (mask >= (1<<N)-1) return 0;
    if (dp[mask] != -1) return dp[mask];

    int ret = 0;
    for (int i = 0; i < N; i++) { // iは新しく選ぶやつ
        // iを選んだことがあるなら飛ばす
        if (mask & (1 << i)) continue;

        // 新しく選ぶやつに関するスコアを計算
        int score = 0;
        for (int j = 0; j < N; j++) {
            if (mask & (1 << j)) {
                score += item[j][i];
            }
        }

        chmax(ret, rec(mask | (1 << i)) + score);
    }

    return dp[mask] = ret;
}

signed main() {
    cin >> N >> M;
    for (int i = 0; i < M; i++) {
        int a, b, c;
        cin >> a >> b >> c;
        item[a][b] = c;
    }

    fill(dp, dp+(1<<9), -1);

    int ans = rec(0);

    cout << ans << endl;

    return 0;
}

要点

  • $N!​$をしたい気持ちになったらbitDPができるって強い人が言ってた
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1