1. 程式人生 > 其它 >[噼昂!]water(Pending)

[噼昂!]water(Pending)

目錄

壹、關於題目 ¶

不想編了。

貳、關於題解 ¶

必須注意到,最終剩下的最高的山一定是前 \(K+1\) 高的。

該特性重要的原因是,接水的高度一定會取決於最高的山,因此我們應該注意到這一關於最高的山的特性。

然後,我們可以設計 \(\rm DP\) 轉移了,設 \(pre(i,j,k,0|1)\) 表示前 \(i\) 座山,目前剩下的最高高度的山是前 \(i\) 座中第 \(j\) 高的,並且保證 \([i+1,n]\) 中也有高度至少為 \(j\) 的山,目前已經移除了 \(k\) 座山,積水體積為 偶數/奇數 的方案數。同時,定義 \(suf(i,j,k,0|1)\)

表示字尾,定義類似。至於 \(j\) 這一維可能有些奇怪,但是到了後面就可以解釋為什麼應該這樣做了。

由於對稱性,只解釋 \(pre\) 的轉移。考慮從 \(i-1,j,k,0|1\) 轉移到 \(i,j',k',w'\).

\(preMax(i,j)\) 表示前 \(i\) 座山中前 \(K+1\) 高的山中第 \(j\) 矮的高度,如果不足,用 \(0\) 表示

先找到 \(j'\) 使得 \(preMax(i-1,j)=preMax(i,j')\),如果找不到,則 \(j'=0\)(表示最小的),然後,我們分開考慮是否移除當前的山;

  • 如果移除該山,顯然前提條件是 \(k<K\),那麼該位的積水就應該是上一次最高的山的高度,為 \(preMax(i-1,j)\)
  • 如果該山保留,那麼轉移不一定到 \(j'\),因為 \(i\) 這座山也有可能成為最高山,所以需要比較一下 \(preMax(i-1,j)\)\(h_i\) 的大小,如果 \(h_i\) 更大,那麼應該轉移到使得 \(preMax(i,t)=h_i\)\(t\) 位,否則,仍然轉移到 \(j'\)

至於 \(suf()\) 也是類似的轉移。另外,我們需要考慮合併 \(pre()\)\(suf()\),具體即列舉一座前 \(K+1\) 高的山,然後將 \(pre(i-1,p)\)\(suf(i+1,s)\) 進行合併,注意,此處需要保證 \(preMax(i-1,p)\le h_i\)

並且 \(sufMax(i+1,s){\color{red}<}h_i\),保證兩者都小於等於原因是我們的狀態定義就要保證第二維後面還有一座高於它的山,至於為什麼 \(sufMax\) 不應該取等,是為了防止計算重複。

時間複雜度 \(\mathcal O(nk^2)\),不過我寫得不是很好,常數過大。

叄、參考程式碼 ¶

#pragma GCC optimize("Ofast")

#include <bits/stdc++.h>
using namespace std;
 
// # define USING_STDIN
// # define NDEBUG
// # define NCHECK
#include <cassert>
 
namespace Elaina {

#define rep(i, l, r) for(int i = (l), i##_end_ = (r); i <= i##_end_; ++i)
#define drep(i, l, r) for(int i = (l), i##_end_ = (r); i >= i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define whole(v) ((v).begin()), ((v).end())
#define bitcnt(s) (__builtin_popcount(s))
#ifdef NCHECK
# define iputs(Content) ((void)0)
# define iprintf(Content, argvs...) ((void)0)
#else
# define iputs(Content) fprintf(stderr, Content)
# define iprintf(Content, argvs...) fprintf(stderr, Content, argvs)
#endif
 
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair <int, int> pii;
    typedef pair <ll, ll> pll;
    
    template <class T> inline T fab(T x) { return x < 0 ? -x : x; }
    template <class T> inline void getmin(T& x, const T rhs) { x = min(x, rhs); }
    template <class T> inline void getmax(T& x, const T rhs) { x = max(x, rhs); }
 
#ifndef USING_STDIN
    inline char freaGET() {
# define BUFFERSIZE 1 << 17
        static char BUF[BUFFERSIZE], *p1 = BUF, *p2 = BUF;
        return p1 == p2 && (p2 = (p1 = BUF) + fread(BUF, 1, BUFFERSIZE, stdin), p1 == p2) ? EOF : *p1++;
# undef BUFFERSIZE
    }
# define CHARGET freaGET()
#else
# define CHARGET getchar()
#endif
    template <class T> inline T readret(T x) {
        x=0; int f = 0; char c;
        while((c = CHARGET) < '0' || '9' < c) if(c == '-') f = 1;
        for(x = (c^48); '0' <= (c = CHARGET) && c <= '9'; x = (x << 1) + (x << 3) + (c ^ 48));
        return f ? -x : x;
    }
    template <class T> inline void readin(T& x) { x = readret(T(1)); }
    template <class T, class... Args> inline void readin(T& x, Args&... args) {
        readin(x), readin(args...);
    }

    template <class T> inline void writc(T x, char s = '\n') {
        static int fwri_sta[55], fwri_ed = 0;
        if(x < 0) putchar('-'), x = -x;
        do fwri_sta[++fwri_ed] = x % 10, x /= 10; while(x);
        while(putchar(fwri_sta[fwri_ed--] ^ 48), fwri_ed);
        putchar(s);
    }

} using namespace Elaina;

/**
 * @param MOD used for modulo
 * @param RT the primitive root of @p MOD
*/
template <int MOD, int RT> struct Mint {
    int val;
    static const int mod = MOD;
    Mint(ll v = 0) { val = int(-mod < v && v < mod ? v : v % mod); if(val < 0) val += mod; }
    inline friend bool operator == (const Mint& a, const Mint& b) { return a.val == b.val; }
    inline friend bool operator != (const Mint& a, const Mint& b) { return !(a == b); }
    inline friend bool operator < (const Mint& a, const Mint& b) { return a.val < b.val; }
    inline friend bool operator > (const Mint& a, const Mint& b) { return a.val > b.val; }
    inline friend bool operator <= (const Mint& a, const Mint& b) { return a.val <= b.val; }
    inline friend bool operator >= (const Mint& a, const Mint& b) { return a.val >= b.val; }
    inline Mint& operator += (const Mint& rhs) { return (*this) = Mint((*this).val + rhs.val); }
    inline Mint& operator -= (const Mint& rhs) { return (*this) = Mint((*this).val - rhs.val); }
    inline Mint& operator *= (const Mint& rhs) { return (*this) = Mint(1ll * (*this).val * rhs.val); }
    inline Mint operator - () const { return Mint(-val); }
    inline Mint& operator ++ () { return (*this) = (*this) + 1; }
    inline Mint& operator -- () { return (*this) = (*this) - 1; }
    inline friend Mint operator + (Mint a, const Mint& b) { return a += b; }
    inline friend Mint operator - (Mint a, const Mint& b) { return a -= b; }
    inline friend Mint operator * (Mint a, const Mint& b) { return a *= b; }
    inline friend Mint qkpow(Mint a, ll n) {
        assert(n >= 0); Mint ret = 1;
        for(; n; n >>=1, a *= a) if(n & 1) ret *= a;
        return ret;
    }
    inline friend Mint inverse(Mint a) { assert(a != 0); return qkpow(a, mod - 2); }
};
using mint = Mint <1000000007, 5>;

const int maxn = 25000;
const int maxk = 26;
const int mod = 1e9 + 7;

int h[maxn + 5], n, K;

inline void input() {
    readin(n, K);
    rep(i, 1, n) readin(h[i]);
}

namespace Dynamic_programming {

    set <pii> S;

    int preMax[maxn + 5][maxk + 5];
    int preMax_cnt[maxn + 5], idpre[maxn + 5];
    mint pre[maxn + 5][maxk + 5][maxk + 5][2];
    inline void solvePre() {
        rep(i, 1, n) {
            S.insert({h[i], i});
            if((int)S.size() > K + 1) S.erase(S.begin());
            for(auto x : S) {
                preMax[i][++preMax_cnt[i]] = x.fi;
                if(x.fi == h[i]) idpre[i] = preMax_cnt[i];
            }
        }
        pre[0][0][0][0] = 1;
        rep(i, 1, n) rep(j, 0, preMax_cnt[i - 1]) {
            int nxt = 0;
            /// find the current id
            for(int p = 0; p <= preMax_cnt[i]; ++p)
                if(preMax[i - 1][j] == preMax[i][p])
                    nxt = p;
            rep(p, 0, K) {
                rep(bit, 0, 1) {
                    /// try to remove @p i
                    if(p < K) {
                        int val = (bit + preMax[i][nxt]) & 1;
                        pre[i][nxt][p + 1][val] += pre[i - 1][j][p][bit];
                    }
                    /// do not remove @p i
                    if(h[i] > preMax[i - 1][j]) // it becomees the highest Mount
                        pre[i][idpre[i]][p][bit] += pre[i - 1][j][p][bit];
                    else {
                        int val = (bit + preMax[i - 1][j] - h[i]) & 1;
                        pre[i][nxt][p][val] += pre[i - 1][j][p][bit];
                    }
                }
            }
        }
    }

    int sufMax[maxn + 5][maxk + 5];
    int sufMax_cnt[maxn + 5], idsuf[maxn + 5];
    mint suf[maxn + 5][maxk + 5][maxk + 5][2];
    inline void solveSuf() {
        S.clear();
        drep(i, n, 1) {
            S.insert({h[i], -i}); // When same heit, save the latter one
            if((int)S.size() > K + 1) S.erase(S.begin());
            for(auto x : S) {
                sufMax[i][++sufMax_cnt[i]] = x.fi;
                if(x.fi == h[i]) idsuf[i] = sufMax_cnt[i];
            }
        }
        suf[n + 1][0][0][0] = 1;
        drep(i, n, 1) rep(j, 0, sufMax_cnt[i + 1]) {
            int nxt = 0;
            for(int p = 0; p <= sufMax_cnt[i]; ++p)
                if(sufMax[i + 1][j] == sufMax[i][p])
                    nxt = p;
            rep(p, 0, K) rep(bit, 0, 1) {
                // try to remove
                if(p < K) {
                    int val = (bit + sufMax[i][nxt]) & 1;
                    suf[i][nxt][p + 1][val] += suf[i + 1][j][p][bit];
                }
                if(h[i] > sufMax[i + 1][j])
                    suf[i][idsuf[i]][p][bit] += suf[i + 1][j][p][bit];
                else {
                    int val = (bit + sufMax[i + 1][j] - h[i]) & 1;
                    suf[i][nxt][p][val] += suf[i + 1][j][p][bit];
                }
            }
        }
    }

} // using Dynamic_programming;

namespace Combine {

    using namespace Dynamic_programming;

    mint ans;
    inline void launch() {
        S.clear();
        rep(i, 1, n) {
            S.insert({h[i], i});
            if((int)S.size() > K + 1)
                S.erase(S.begin());
        }
        for(auto x : S) {
            int i = x.se;
            for(int p = 0; p <= preMax_cnt[i - 1]; ++p) if(preMax[i - 1][p] <= x.fi)
                for(int s = 0; s <= sufMax_cnt[i + 1]; ++s) if(sufMax[i + 1][s] < x.fi /* avoid calc the same situation */ ) {
                    rep(t, 0, K) {
                        ans += pre[i - 1][p][t][0] * suf[i + 1][s][K - t][0];
                        ans += pre[i - 1][p][t][1] * suf[i + 1][s][K - t][1];
                    }
                }
        }
        writc(ans.val);
    }

} // using namespace Combine;

signed main() {
    // freopen("rain.in", "r", stdin);
    // freopen("rain.out", "w", stdout);
    input();
    Dynamic_programming::solvePre();
    Dynamic_programming::solveSuf();
    Combine::launch();
    return 0;
}

肆、關鍵 の 地方 ¶

影響問題的因素,往往應該被 \(\rm DP\) 包含,同時應該注意那些較小的變數,他們可能是問題的關鍵。而那些既影響問題,同時又被特殊設計的變數,那就更應該被考慮。