チラチラチラ裏

\主に競プロ備忘録/

ARC 070 D - No Need

問題概要

N個の自然数a[i] (1<=i<=N)と自然数Kが与えられる。
その組み合わせからなる2Nの集合を考え、そのうち部分和がKを超えるものを良い集合とする。
a[i]を含む任意の良い集合Aに対し、A\ {a[i]}(Aからa[i]を除いた集合)も良い集合であるならばa[i]は不必要であるとする。
a[i] (1<=i<=N)のうち、不必要な自然数の個数を求めよ。
1<=N,K<=5*103,1<=a[i]<=109(部分点解法は1<=N,K<=400)

解法

まず、a[i]>=Kならばa[i]は必要であるため、1<=a[i]<Kなるa[i]についてのみ考えれば良い。
よって1<=a[i]<=5*103となる。
不必要の定義は次のように言い換えられる。 「a[i]を除いた部分和でK-a[i]<=sum<Kなるものが存在するならa[i]は不必要でない」

部分点解法から。
dp[i][sum] := (iを除いて部分和sumを作れるか)として、iを除いた時の部分和を列挙していく。 for(k:K+1->0) for(j!=i) dp[i][k]] |= dp[i][k-a[j]] という漸化式に従う。
各iについて計算するとO(N3)となる。

a[i] >= a[j] でa[i]が不必要ならばa[j]も不必要であるという関係が成り立つことに注意すると、二分探索できることがわかる。
O(N2*logN)になり、間に合う。

また、 dp1[i][sum] := (a[j] (1<=j<=i)までの部分和でsumが作れるか)
dp2[i][sum] := (a[j] (i<=j<=N) ..)
として各DPを計算しておく。
a[j] (1<=j<i)の部分和x、a[j] (i<j<=N)の部分和yとして、 各iについてxが作れるならば、K-a[i] <= x+y < Kなるyが取れれば条件を満たすため、 K-a[i]-y <= y < K-xを満たすyが存在するかを判定すれば良い。yに関する累積和を作っておけばこれはO(K)で判定できる。 よって全体でO(NK)=O(N2)で解けることになる。

メモ

k:0->K+1とすると0->a[j]->2*a[j]とtrueになっていってしまう。(よくやるミス) そうならないためにはもう一次元増やすか、bitsetでやると良い。
最速解法見てるとO(NlogN)で解けてるっぽいんだけど全然わからん。

コード

部分点

#include<stdio.h>
#include<iostream>
#include<string>
#include<vector>
#include<map>
#include<set>
#include<list>
#include<queue>
#include<deque>
#include<algorithm>
#include<utility>
#include<memory>
#include<cmath>
#include<stack>
#include<tuple>
#include<numeric>
#include<cassert>

#define ALL(g) (g).begin(),(g).end()
#define REP(i, x, n) for(int i = x; i < n; i++)
#define rep(i,n) REP(i,0,n)
#define EXIST(s,e) ((s).find(e)!=(s).end())
#define pb push_back
#define DEBUG false

using namespace std;

using ll = long long;
using P = pair<int,int>;
using Pl = pair<int,ll>;
using Pd = pair<double,double>;
using T = tuple<double,double,int>;

const int mod=1e9+7,INF=1<<30;
const double EPS=1e-12,PI=3.1415926535897932384626;
const ll LINF=1LL<<60;
const int MAX_N = 5003;
const ll lmod = 1e9+7;

int N,K,a[MAX_N];
bool dp[MAX_N][MAX_N*2];

int main(){
  scanf("%d%d",&N,&K);
  int temp = 0;
  rep(i,N){
    int b;
    scanf("%d",&b);
    if(b<K) a[temp++] = b;
  }
  N = temp;
  fill(dp[0],dp[MAX_N-1],false);
  rep(i,N) dp[i][0] = true;
  int ans = 0;
  rep(i,N){
    rep(j,N){
      if(j==i) continue;
      for(int k=K+1; k>=a[j]; k--){
        dp[i][k] |= dp[i][k-a[j]];
      }
    }
    bool need = false;
    REP(j,K-a[i],K) need |= (dp[i][j]);
    if(!need) ans++;
  }
  cout << ans << endl;
  return 0;
}

満点

#include<stdio.h>
#include<iostream>
#include<string>
#include<vector>
#include<map>
#include<set>
#include<list>
#include<queue>
#include<deque>
#include<algorithm>
#include<utility>
#include<memory>
#include<cmath>
#include<stack>
#include<tuple>
#include<numeric>
#include<cassert>

#define ALL(g) (g).begin(),(g).end()
#define REP(i, x, n) for(int i = x; i < n; i++)
#define rep(i,n) REP(i,0,n)
#define EXIST(s,e) ((s).find(e)!=(s).end())
#define pb push_back
#define DEBUG false

using namespace std;

using ll = long long;
using P = pair<int,int>;
using Pl = pair<int,ll>;
using Pd = pair<double,double>;
using T = tuple<double,double,int>;

const int mod=1e9+7,INF=1<<30;
const double EPS=1e-12,PI=3.1415926535897932384626;
const ll LINF=1LL<<60;
const int MAX_N = 5003;
const ll lmod = 1e9+7;

int N,K,a[MAX_N];
bool dp[MAX_N*2];

int main(){
  scanf("%d%d",&N,&K);
  int temp = 0;
  a[temp++] = 0; //dummy
  rep(i,N){
    int b;
    scanf("%d",&b);
    if(b<K) a[temp++] = b;
  }
  N = temp;
  sort(a,a+N);
  a[N] = K; //dummy
  int l = 0,r = N+1,mid;
  while(abs(r-l)>1){
    mid = (l+r)/2;
    fill(dp,dp+K,false);
    dp[0] = true;
    rep(j,N){
      if(j==mid) continue;
      for(int k=K; k>=a[j]; k--){
        dp[k] |= dp[k-a[j]];
      }
    }
    bool need = false;
    REP(j,K-a[mid],K) need |= (dp[j]);
    if(!need) l = mid;
    else r = mid;
  }
  cout << l << endl;
  return 0;
}