ちゃっくのメモ帳

ちゃっくがメモしときたいことをメモしとくよ

ARC070 D No Need

問題

D - No Need

[改訂第3版]C++ポケットリファレンス (POCKET REFERENCE)

[改訂第3版]C++ポケットリファレンス (POCKET REFERENCE)

解法

解説のO(NKlogN)解法。

まず、「カードが(不)必要」がどういう状況か?を考える。
「カードが不必要」とは、カードa_iを含む全てのよい集合が、どれもその総和がK+a_iとなる場合である。なぜならこの場合、a_iを引いてもKを超えるのでよい集合のままだからである。
逆に、「カードが必要」というのはa_iを含む良い集合にその総和がK+a_i未満のものが存在するということである。この場合、a_iを引けばその良い集合は良い集合でなくなる。

これをまとめると、a_iを含まない部分集合にその総和x K-a_i \leq x < Kのものがある場合、a_iは必要なカードになる。そうでない場合、不必要なカードになる。

DPの部分については典型的なDPなので省略(このDPはTypical DP contest A問題の簡易版)。

解説PDFの「カードiが不要な時、a_j \leq a_iを満たすカードjも不要である」という部分が個人的に引っかかった。
あまり直感的じゃない。

直感的に捉えるなら、「カードjが必要なときa_j \leq a_iとなるカードiも必要である」と考える。
こう考えると、カードjを含む良い集合のうちカードjを抜くと良い集合でなくなる集合においてカードjをカードiに置き換えることで、カードiが必要な気がする。
(例えば和がK+a_j-1となる良い集合にa_iが入っていればa_i (> a_j)を取り除くと良い集合でなくなるし、a_iがなければa_jのかわりにa_iを使えばa_iが必要であることがわかる)

証明すると次のようになる
命題 :  a_jが必要 \Rightarrow a_iが必要(ただし a_j < a_i)
(証明)
前提 :: a_jが必要であることから、ある集合  A = \{a_{k_1},a_{k_2},...,a_{k_m}\}(Aにa_jは含まれない)が存在しその和xはK-a_j \leq x < Kを満たす。

もし、a_i \not \in Aの場合、Aをそのまま用いれば K-a_i < K-a_j \leq x \leq Kよりa_iも必要であることが示せる。
逆に、a_i \in Aの場合、Aからa_iを取り除き、a_jを追加すればその集合A'の和x'
K-a_i \leq x' < K - a_i + a_j < Kとなるのでa_iが必要なカードであることがわかる。
(証明終了)

この証明は直感的であるが、解説PDFのように「不必要」ベースで考えた場合の証明も載せておく
命題: a_iが不必要 \Rightarrow a_jも不必要 (ただしa_j < a_i)
(証明)
前提 :: a_iが不必要であるので、任意のa_iを含まない集合Aとその和xがx < K-a_iK \leq xのどちらかを満たす。
この前提から「任意のa_jを含まない集合の和がK-a_j未満かK以上である」ことを示せば良い。

a_jを含まない任意の集合のうち、a_iも含まない集合は前提より自明。a_jを含まない任意の集合のうち、a_iを含むものについて考える。a_jを含まず、a_iを含む集合はa_i,a_jを含まない集合にa_iを追加したものである。
ここでa_i,a_jを含まない集合の制約について考える。
まず前提よりx < K-a_iK \leq xを満たす。
もし、xがK-a_i-a_jよりも大きい場合、その集合にa_jを追加した集合はK-a_i <= xになりうる。この集合はa_iを含まない集合なので前提をみたすはずだがK-a_i<=xとなりうることは前提と矛盾する。
従って、a_i,a_jを含まない集合はK-a_i-a_jより小さい。
よって、a_i,a_jを含まない集合はK-a_i-a_j未満かK以上となる。
これにa_iを追加した集合はK-a_j未満かK+a_i>=K以上となる。
故に、a_jを含まない集合はK-a_j未満かK以上となるのでa_jは不要であることがわかる。
(証明終了)

これが証明できればコードは自明

bool need(int index,int N,int K,const vector<ll>& a){
    vector<bool> e(K+1,false);
    e[0] = true;
    for(int i=0;i<N;i++){
        if(i==index) continue;
        for(int j=K;j>=0;j--){
            if(e[j] and j+a[i]<=K) e[j+a[i]]=true;
        }
    }
    for(int i=max<ll>(0,K-a[index]);i<K;i++) if(e[i]) return true;
    return false;
}

int main(){
    int N,K;
    cin >> N >> K;
    vector<ll> a(N);
    rep(i,N) cin >> a[i];
    sort(all(a));

    if(need(0,N,K,a)){
        cout << 0 << endl;
        return 0;
    }
    if(!need(N-1,N,K,a)){
        cout << N << endl;
        return 0;
    }

    int l,r;    // l : NG , r : OK
    l = -1;     // NG
    r = N-1;    // OK
    while(r-l>1){
        int mid=(l+r)/2;
        bool ret = need(mid,N,K,a);
        if(ret){
            r = mid;
        }else{
            l = mid;
        }
    }
    cout << l+1 << endl;
}