ferinの競プロ帳

競プロについてのメモ

ICPC 2019 Asia Yokohama Regional E Reordering the Documents

問題ページ
問題を整理すると,長さnの数列を長さm以下の増加列に分解する方法が何通りあるか?となる.s_i  \gt  s_j\ (i \lt j) であるような i,j が同じ増加列に属することはない.逆にこれ以外の i,j についてはどのように増加列に振り分けても問題ない.

「上述のような i,j をつないだグラフを白/黒の2色で塗り分ける.隣り合う頂点を違う色で塗る かつ 同じ色はm個以下となる塗り方は何通りあるか?」という問題になった.もしグラフが二部グラフでないとすると,条件を満たす塗り方は存在しないので答えは0になる.グラフが二部グラフの場合,色の塗り方は各連結成分で2通りしかない.各連結成分でどちらの塗り方をするか?というDPを行うことで数え上げることができる.

\text{dp} \lbrack i番目の連結成分まで \rbrack  \lbrack 白色がj \rbrack = 何通りあるか というDPテーブルを持つ.i番目の連結成分について,白・黒の頂点数がabであるとき,遷移は

  • \text{dp} \lbrack i+1 \rbrack  \lbrack j+a \rbrack  += \text{dp} \lbrack i \rbrack  \lbrack j \rbrack 白をa個,黒をb
  • \text{dp} \lbrack i+1 \rbrack  \lbrack j+b \rbrack  += \text{dp} \lbrack i \rbrack  \lbrack j \rbrack 白をb個,黒をa

となる.

#include <bits/stdc++.h>  
using namespace std;  
using ll = long long int;  
using PII = pair<ll,ll>;  
#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)  
#define REP(i, n) FOR(i, 0, n)  
struct FastIO {FastIO() { cin.tie(0); ios::sync_with_stdio(0); }}fastiofastio;  
ll MOD = 1000000007;  
  
signed main() {  
    ll n, m;  
    cin >> n >> m;  
    vector<ll> a(n);  
    REP(i, n) cin >> a[i];  
  
    vector<vector<ll>> g(n);  
    REP(i, n) FOR(j, i+1, n) {  
        if(a[i] > a[j]) {  
            g[i].push_back(j);  
            g[j].push_back(i);  
        }  
    }  
  
    ll cnt0, cnt1;  
    vector<ll> col(n);  
    function<bool(ll,ll)> dfs = [&](ll v, ll c) {  
        if(c == -1) cnt0++;  
        else cnt1++;  
        col[v] = c;  
        for(auto to: g[v]) {  
            if(col[to] == c) return false;  
            if(col[to] == 0 && !dfs(to, -c)) return false;  
        }  
        return true;  
    };  
  
    vector<PII> v;  
    REP(i, n) {  
        if(col[i] == 0) {  
            cnt0 = cnt1 = 0;  
            bool ret = dfs(i, -1);  
            if(!ret) {  
                cout << 0 << endl;  
                return 0;  
            }  
            v.emplace_back(cnt0, cnt1);  
        }  
    }  
  
    const ll sz = v.size();  
    vector<ll> s(sz);  
    REP(i, sz) s[i] = v[i].first + v[i].second;  
    FOR(i, 1, sz) s[i] += s[i-1];  
  
    vector<vector<ll>> dp(sz+1, vector<ll>(m+1));  
    dp[0][0] = 1;  
    REP(i, sz) REP(j, m+1) {  
        if(j+v[i].first<=m && s[i]-j-v[i].first<=m) {  
            (dp[i+1][j+v[i].first] += dp[i][j]) %= MOD;  
        }  
        if(j+v[i].second<=m && s[i]-j-v[i].second<=m) {  
            (dp[i+1][j+v[i].second] += dp[i][j]) %= MOD;  
        }  
    }  
  
    ll ret = 0;  
    REP(i, m+1) (ret += dp[sz][i]) %= MOD;  
    cout << ret << endl;  
}