競技プログラミング日記

主に AtCoder の記事です

AtCoder Beginner Contest 321F問題

 

ABC321F

問題概要

Muliset に対する部分和問題に,追加と削除クエリを与えたもの. 俗に戻すDPと呼ばれる.

解法

(0) まず,multiset が固定されている場合を考える. これは普通の部分和DP. とくに,配列を 1次元にした inplace DP で考えると, 今回の問題に応用するときに楽.
(1) 次に,multiset に1つ元 \(x\) を追加または削除することを考える. 簡単な追加の方を考える. (0)の考えを用いると,

\(dfor \ i \in [x,k]\)
   \(dp_{i} += dp_{i-x}\)

という処理をしている.
削除の方も同様に,

\(for \ i \in [x,k]\)    \(dp_{i} -= dp_{i-x}\)

と出来る.for の順番が反転することと,\(+\) と \(-\) も反転している.

注意

多項式を使った数え上げを考えると,操作が可換であることが用意に分かる. \(t\) に関する多項式とする. \(x\) を mutiset に追加することは,多項式に \(1+t^{x}\) を掛けることに対応して, \(x\) を mutiset から削除することは,多項式に \(1+t^{x}\) で割ることに対応する. 積と割り算は共に可換.
また,Multiset から元を選んで部分和を作るとき,一つの元に対して 1回しか使えないというルールなので \(dfor \ i \in [x,k]\)    \(dp_{i} += dp_{i-x}\) としている. もし,1つの元を何度でも使えるというルールなら, \(for\) になる.

使っている記号,マクロ等 "https://ecsmtlir.hatenablog.com/entry/2022/12/23/131925"

int main() {
  ll q,k; cin >> q >> k;

  vector<mint> dp(k+1);
  dp[0] = 1;
  rep(qi,q){
    char c; ll x; cin >> c >> x;
    if(c == '+'){
      for(int i = k; i >= x; i--){
        dp[i] += dp[i-x];
      }
    }else{
      for(int i = x; i <= k; i++){
        dp[i] -= dp[i-x];
      }
    }
   
    cout << dp[k].val() << endl;
  }
 
  return 0;
}