Problem p03780 (C++)

Buggy submission (status: Time Limit Exceeded):
#include <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <deque>
#include <functional>
#include <iostream>
#include <iterator>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

using namespace std;

template <typename T, size_t N> constexpr size_t NELEMS(T (&)[N]) { return N; }

#define FOR(i, start, end) for (int i = (start); i < (end); ++i)
#define REP(i, n) FOR(i, 0, n)

#define DEBUG
#undef DEBUG

using ll = long long;
using ull = unsigned long long;

int N, K;
ll A[5001];

bool dp[5001][5001]; // i枚目まで使ったとき総和をjにできるか

bool is_needed(int k) {
  bool need = false;

  if (A[k] >= K)
    return true; // 集合 {k} が存在するためkは必要

  // 一時的にカードkを取り除く
  ll tmp = A[k];
  A[k] = 0;

  // DP
  FOR(i, 1, N + 1) {
    FOR(j, 1, K) {
      // i枚目を使うか使わないかの2択
      // 使う場合、dp[i-1][j-A[i]]
      // 使わない場合、dp[i-1][j]
      // ただし j-A[i] < 0 ならば使えない
      if (j - A[i] < 0) {
        dp[i][j] = dp[i - 1][j];
      } else {
        dp[i][j] = dp[i - 1][j - A[i]] || dp[i - 1][j];
      }
    }
  }
  // N枚目まで使って総和を [K-tmp, K-1] にできるならkは必要
  FOR(j, K - tmp, K) {
    if (dp[N][j])
      need = true;
  }

  // カードkを戻す
  A[k] = tmp;

  return need;
}

void solve() {
  // ソートを忘れずに!!
  sort(A + 1, A + N + 1);

  FOR(i, 0, N + 1) { dp[i][0] = true; }
  FOR(j, 1, K + 1) { dp[0][j] = false; }

  // iが不要であるとき、A[j] <= A[i] なるjも不要
  // もしjが必要だったとすると、jが必須であるようなよい集合がとれる
  // が、その中のjをiに置き換えたものもiが必須であるようなよい集合だ
  // から矛盾する。
  //
  // よって単調性が成り立ち、二分探索できる
  // 不明なカードの範囲を狭めていく
  // 最初は [1,N] が不明
  int cnt;
  int kmax = N;
  int kmin = 1;
  for (;;) {
    int k = (kmax + kmin) / 2;
    if (is_needed(k)) {
      if (kmax == kmin) {
        cnt = k - 1;
        break;
      }
      kmax = k - 1;
    } else {
      if (kmax == kmin) {
        cnt = k;
        break;
      }
      kmin = k + 1;
    }
  }

  cout << cnt << "\n";
}

int main() {
  cin >> N >> K;
  FOR(i, 1, N + 1) { cin >> A[i]; }

  solve();

  return 0;
}


Fixed submission:
#include <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <deque>
#include <functional>
#include <iostream>
#include <iterator>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

using namespace std;

template <typename T, size_t N> constexpr size_t NELEMS(T (&)[N]) { return N; }

#define FOR(i, start, end) for (int i = (start); i < (end); ++i)
#define REP(i, n) FOR(i, 0, n)

#define DEBUG
#undef DEBUG

using ll = long long;
using ull = unsigned long long;

int N, K;
ll A[5001];

bool dp[5001][5001]; // i枚目まで使ったとき総和をjにできるか

bool is_needed(int k) {
  bool need = false;

  if (A[k] >= K)
    return true; // 集合 {k} が存在するためkは必要

  // 一時的にカードkを取り除く
  ll tmp = A[k];
  A[k] = 0;

  // DP
  FOR(i, 1, N + 1) {
    FOR(j, 1, K) {
      // i枚目を使うか使わないかの2択
      // 使う場合、dp[i-1][j-A[i]]
      // 使わない場合、dp[i-1][j]
      // ただし j-A[i] < 0 ならば使えない
      if (j - A[i] < 0) {
        dp[i][j] = dp[i - 1][j];
      } else {
        dp[i][j] = dp[i - 1][j - A[i]] || dp[i - 1][j];
      }
    }
  }
  // N枚目まで使って総和を [K-tmp, K-1] にできるならkは必要
  FOR(j, K - tmp, K) {
    if (dp[N][j])
      need = true;
  }

  // カードkを戻す
  A[k] = tmp;

  return need;
}

void solve() {
  // ソートを忘れずに!!
  sort(A + 1, A + N + 1);

  FOR(i, 0, N + 1) { dp[i][0] = true; }
  FOR(j, 1, K + 1) { dp[0][j] = false; }

  // iが不要であるとき、A[j] <= A[i] なるjも不要
  // もしjが必要だったとすると、jが必須であるようなよい集合がとれる
  // が、その中のjをiに置き換えたものもiが必須であるようなよい集合だ
  // から矛盾する。
  //
  // よって単調性が成り立ち、二分探索できる
  // 不明なカードの範囲を狭めていく
  // 最初は [1,N] が不明
  int cnt;
  int kmax = N;
  int kmin = 1;
  for (;;) {
    int k = (kmax + kmin) / 2;
    if (is_needed(k)) {
      if (kmax == kmin) {
        cnt = k - 1;
        break;
      }
      kmax = k;
    } else {
      if (kmax == kmin) {
        cnt = k;
        break;
      }
      kmin = k + 1;
    }
  }

  cout << cnt << "\n";
}

int main() {
  cin >> N >> K;
  FOR(i, 1, N + 1) { cin >> A[i]; }

  solve();

  return 0;
}