[噼昂!]operator(Pending)

壹、关于题目 ¶

仍然是没有时间去编......

贰、关于题解 ¶

竟然没有往括号树上面想......伊蕾娜大失败 😦 .

括号限制优先级,没有括号则可以直接 \(\rm DP\) 来做,而优先级,我们选择使用括号树对其进行翻译,然后,我们的 \(\rm DP\) 过程就变成了树上背包。

\(\rm DP\) 转移中有个关键处理的地方 —— 尾随符(\(\rm final\;note\))以及表达式(\(\rm Expression\))的划分。

具体来说,我们应该维护两个东西:

  • \(dp_{u,i}\),表示节点 \(u\) 一共使用了 \(i\) 个加号(不包含尾随符)所有方案的和;
  • \(tmp\_dp_{i}\) 表示使用了 \(i\) 个加号(包含尾随符),每种方案 不包括最后一段连续乘法值 的和;
  • \(cnt_i\),表示使用 \(i\) 个加号(不含尾随符)的方案数;
  • \(tmp\_cnt_i\),表示使用 \(i\) 个加号(包含尾随符)的方案数;
  • \(prod_i\),表示使用了 \(i\) 个加号,每种方案 最后一段连续乘法值 的和;

\(\rm DP\) 转移的时候,我们应当将前面的一堆儿子合并之后得到的 \(tmp\_dp,tmp\_cnt\) 与当前儿子的 \(dp,cnt\) 进行合并,并考察新的尾随符填 $+/\times $ 的不同贡献。

注意转移的时候不能直接改数组,还应该新开一个缓存数组储存,计算完毕之后拷贝进 \(tmp\) 数组与 \(prod\) 数组。

在处理最后一个儿子的时候,就不需要考虑新的尾随符了,所有的贡献可以直接计入 \(dp\)\(cnt\) 数组。因为最后一个儿子象征着反括号,所有的这些都被合并为一个整体了。

看上去好像是 \(\mathcal O(n^3)\) 的,实际上是 \(\mathcal O(\max siz_u^2)(\sum siz = n)\),最劣情况为 \(\mathcal O(n^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 = 1e5;
const int maxm = 2500;

int plus_require, n;
int val[maxn + 5];
char s[maxn + 5];

inline void input() {
    readin(n, plus_require); scanf("%s", s + 1);
}

int nxt[maxn + 5];
namespace Build {

    int stk[maxn + 5], ed;
    inline void launch() {
        rep(i, 1, n) {
            if(s[i] == '(') stk[++ed] = i;
            else if(s[i] == ')')
                nxt[stk[ed--]] = i;
        }
    }

} // using namespace Build;

namespace Work {

    mint dp[maxm + 5][maxm + 5]; // not include the final note (for there's no note)
    mint cnt[maxm + 5][maxm + 5];
    int siz[maxm + 5], ncnt;
    vector <int> g[maxm + 5];
    int solve(int l, int r) {
        // the whole bracket
        if(nxt[l] && nxt[l] == r) return solve(l + 1, r - 1);

        /* <-- get the current node --> */
        int u = 0, i = l;
        /// the first element is a bracket
        if(s[l] == '(') u = solve(l + 1, nxt[l] - 1), i = nxt[l] + 1;
        else { /// otherwise a number
            u = ++ncnt, dp[u][0] = 0, cnt[u][0] = 1, siz[u] = 1;
            while(isdigit(s[i])) dp[u][0] = dp[u][0] * 10 + (s[i++] ^ 48);
        }

        /* <-- get the sons --> */
        while(i <= r) {
            while(s[i] == '-') ++i;
            int v;
            /// a bracket
            if(s[i] == '(') v = solve(i + 1, nxt[i] - 1), i = nxt[i] + 1;
            else { /// a number
                v = ++ncnt, dp[v][0] = 0, cnt[v][0] = 1, siz[v] = 1;
                while(isdigit(s[i])) dp[v][0] = dp[v][0] * 10 + (s[i++] ^ 48);
            }
            g[u].push_back(v);
        }
        if(g[u].empty()) return u; /// just a leaf
        
        /** @brief maintain the current @p dp with the counter of %+ (include the final note) */
        static mint tmp_cnt[maxm + 5] = {};
        /** @brief  maintain the current @p dp and also the note following (NOT include the final product) */
        static mint tmp_dp[maxm + 5] = {};
        /** @brief  maintain the product of using @p i %+ (include the final note) */
        static mint prod[maxm + 5] = {};

        /// transfer the son's data into the tmp array & clear the @p dp and @p cnt
        /// enumerate the number of %+
        for(int j = 0; j < siz[u]; ++j) {
            tmp_cnt[j] += cnt[u][j];
            tmp_cnt[j + 1] += cnt[u][j];

            /// no this statement, if the final note is %* then this is a prod
            /// which isn't included in the definition of @p tmp_dp
            // tmp_dp[i] += dp[u][i];

            tmp_dp[j + 1] += dp[u][j];
            prod[j] += dp[u][j];
            prod[j + 1] += cnt[u][j];

            dp[u][j] = cnt[u][j] = 0; /// clear for @p dp
        }

        static mint cache_tmpDp[maxm + 5] = {};
        static mint cache_tmpCnt[maxm + 5] = {};
        static mint cache_prod[maxm + 5] = {};

        int lst_son = g[u].back();
        for(int v : g[u]) {
            /// for the first part include the final note, the number of %+ can reach the upper limit of @p siz[u]
            for(int j = 0; j <= siz[u]; ++j) {
                for(int k = 0; k < siz[v]; ++k) {
                    cache_tmpDp[j + k] += tmp_dp[j] * cnt[v][k];
                    cache_tmpDp[j + k + 1] += tmp_dp[j] * cnt[v][k] + prod[j] * dp[v][k];
                    cache_tmpCnt[j + k] += tmp_cnt[j] * cnt[v][k];
                    cache_tmpCnt[j + k + 1] += tmp_cnt[j] * cnt[v][k];
                    cache_prod[j + k] += prod[j] * dp[v][k];
                    cache_prod[j + k + 1] += tmp_cnt[j] * cnt[v][k];
                    if(v == lst_son) { /// the last son, need to update @p dp
                        dp[u][j + k] += tmp_dp[j] * cnt[v][k] + prod[j] * dp[v][k];
                        cnt[u][j + k] += tmp_cnt[j] * cnt[v][k];
                    }
                }
            }
            /// transfer the cache into the variable
            for(int j = 0; j <= siz[u] + siz[v]; ++j) {
                tmp_dp[j] = cache_tmpDp[j];
                tmp_cnt[j] = cache_tmpCnt[j];
                prod[j] = cache_prod[j];
                cache_tmpDp[j] = cache_tmpCnt[j] = cache_prod[j] = 0;
            }

            siz[u] += siz[v];
        }

        for(int j = 0; j <= siz[u]; ++j)
            tmp_dp[j] = tmp_cnt[j] = prod[j] = 0;

        g[u].clear(); /// a node may be used many times

        return u;
    }
    inline void launch() {
        int rt = solve(1, n);
        writc(dp[rt][plus_require].val);
    }

} // using namespace Work;

signed main() {
    // freopen("operator.in", "r", stdin);
    // freopen("operator.out", "w", stdout);
    input();
    Build::launch();
    Work::launch();
    return 0;
}

肆、关键 の 地方 ¶

括号等优先级问题,可以转到树上,每颗子树实际上就天然地限定了优先级。

另外,树上背包看上去十分暴力,但是用琴生分析一下,最劣情况只出现在其中某个 \(siz\) 最大,这个时候达到最劣的 \(\mathcal O(n^2)\).

posted @ 2021-10-05 22:23  Arextre  阅读(41)  评论(0编辑  收藏  举报