博客

  • CF1155D Beaufitul Array

    送分DP。

    #include <algorithm>
    #include <cmath>
    #include <iostream>
    using int_t = long long int;
    using std::cin;
    using std::cout;
    using std::endl;
    const int_t LARGE = 3e5;
    
    int_t dp[LARGE + 1][3];
    
    int_t n, x;
    
    int main() {
        cin >> n >> x;
        int_t result = 0;
        for (int_t i = 1; i <= n; i++) {
            int_t a;
            cin >> a;
            dp[i][0] = a + std::max(dp[i - 1][0], (int_t)0);
            dp[i][1] = a * x + std::max(dp[i - 1][1], std::max(dp[i - 1][0], 0ll));
            dp[i][2] = a + std::max({dp[i - 1][2], dp[i - 1][1], 0ll});
            result = std::max(result, *std::max_element(dp[i], dp[i] + 3));
        }
        cout << std::max(0ll, result) << endl;
        return 0;
    }

     

  • noi.ac 277 游戏

    见注释。

    另递推求逆元特别慢,效率要求高的地方不要用。

    // #include <iostream>
    #pragma GCC optimize("O3")
    #include <cstdio>
    #include <utility>
    using int_t = long long int;
    // using std::cin;
    // using std::cout;
    // using std::endl;
    
    const int mod = 1e9 + 7;
    const int_t LARGE = 2e7;
    int_t power(int_t base, int_t index) {
        index = (index % (mod - 1) + mod - 1) % (mod - 1);
        int_t result = 1;
        while (index) {
            if (index & 1) result = result * base % mod;
            index >>= 1;
            base = base * base % mod;
        }
        return result;
    }
    
    int fact[LARGE + 1], inv[LARGE + 1], factInv[LARGE + 1], inv2[LARGE + 1];
    inline int C(int n, int m) {
        // if (n < m) return 0;
        return (int_t)fact[n] * factInv[m] % mod * factInv[n - m] % mod;
    }
    //把2n个带标号元素划分成n个大小为2的集合的方案数(集合内无序)
    inline int G(int n) {
        return (int_t)fact[2 * n] * inv2[n] % mod * factInv[n] % mod;
    }
    //至少淘汰了m个玩家的方案数
    inline int F(int n, int m) {
        // if (n < m) return 0;
        if (m == 0) return G(n);
        //前半部分把2n-m个元素划分成m段,每段再塞一个被淘汰的玩家,后半部分钦定剩下的随便选。
        // 2n个元素本身未考虑循环同构。
        return (int_t)2 * n * inv[m] % mod * C(2 * n - m - 1, m - 1) % mod *
               G(n - m) % mod;
    }
    int main() {
        fact[0] = inv[1] = factInv[0] = fact[1] = factInv[1] = 1;
        inv2[0] = 1;
        int n, k;
        // cin >> n >> k;
        scanf("%d%d", &n, &k);
        inv2[1] = (mod - mod / 2);
        for (int i = 2; i <= 2 * n; i++) {
            // inv[i] = (int_t)(mod - mod / i) * inv[mod % i] % mod;
            fact[i] = (int_t)fact[i - 1] * i % mod;
            inv2[i] = (int_t)inv2[i - 1] * inv2[1] % mod;
        }
        // factInv[i] = (int_t)factInv[i - 1] * inv[i] % mod;
        factInv[2 * n] = power(fact[2 * n], -1);
        for (int_t i = 2 * n; i >= 1; i--) {
            inv[i] = (int_t)factInv[i] * fact[i - 1] % mod;
            factInv[i - 1] = (int_t)factInv[i] * i % mod;
        }
        int result = 0;
    
        for (int i = k; i <= n; i++) {
            result = (result + (int_t)(((i - k) & 1) ? (mod - 1) : 1) * C(i, k) %
                                   mod * F(n, i) % mod) %
                     mod;
        }
        printf("%lld\n", (int_t)result * power(G(n), -1) % mod);
        return 0;
    }

     

  • noi.ac 154 Curiosity

    $$ \text{设}f\left( i,j \right) \text{表示第}i\text{次扔骰子是否取到}j\text{这个数,显然这个东西的期望} \\ \text{是}\frac{1}{K}\left( \text{一次扔骰子只能出现一个数} \right) \\ \text{所以}a_i=\sum_{1\le k\le n}{f\left( k,i \right)} \\ \text{所以}E\left( \prod_{1\le i\le L}{a_i^F} \right) =\prod_{1\le i\le L}{\left( \sum_{1\le j\le N}{f\left( j,i \right)} \right) ^F} \\ \text{考虑}\prod_{1\le i\le L}{\left( \sum_{1\le j\le N}{f\left( j,i \right)} \right) ^F}\text{展开后的一项,如果这一项中存在}f\left( i,a \right) \text{和}f\left( i,b \right) ,\text{并且}a\ne b, \\ \text{那么这一整项的期望必须是}0\left( \text{否则出现了不合法情况} \right) \\ \text{对于其他的项,比如}f\left( \text{1,}a \right) ^2f\left( \text{2,}b \right) ^2….,\text{假设这一项由}p\text{个不同的变量构成} \\ \text{那么这一项的期望为}\frac{1}{K^p}\left( \text{独立变量期望可乘} \right) \\ \text{现在我们的问题在于如何计算出}\prod_{1\le i\le L}{\left( \sum_{1\le j\le N}{f\left( j,i \right)} \right) ^F}\text{中由}p\text{个独立变量构成的项的个数。} \\ \text{首先考虑}\left( \sum_{1\le j\le N}{f\left( j,i \right)} \right) ^F,\text{显然这个式子展开后不存在期望为0的项。} \\ \,\,\text{我们忽略项中是由哪些变量}\left( \text{比如}f\left( \text{1,}i \right) ,f\left( \text{2,}i \right) \text{等等} \right) \text{构成的,也忽略掉项之间的顺序关系} \\ \left( \begin{array}{c} \text{在}\left( f\left( \text{1,}i \right) +f\left( \text{2,}i \right) +f\left( \text{3,}i \right) .. \right) \times \left( f\left( \text{1,}i \right) +f\left( \text{2,}i \right) +f\left( \text{3,}i \right) .. \right) \times \left( f\left( \text{1,}i \right) +f\left( \text{2,}i \right) +f\left( \text{3,}i \right) .. \right) \text{中,}f\left( \text{1,}i \right) ^2\times f\left( \text{2,}i \right)\\ \text{会被计算6次}\\ \end{array} \right) \\ \text{则}\left( \sum_{1\le j\le N}{f\left( j,i \right)} \right) ^F\text{展开后忽略上述限制存在}i\text{个无关变量的项的系数和为}S_2\left( F,i \right) ,\text{其中}S_2\text{为第二类斯特林数。} \\ \text{则}S_2\left( F,i \right) \times \left( \begin{array}{c} F\\ i\\ \end{array} \right) \times i!\text{为不忽略限制的符合条件的项的系数和。} \\ \text{构造生成函数}F\left( x \right) =\sum_{i\ge 0}{S_2\left( F,i \right) x^i} \\ \text{则}F\left( x \right) ^L\text{展开后}i\text{次项前的系数即为忽略上述限制后,存在}i\text{个无关变量的项的系数和。} \\ \text{为了还原至原式,将这个系数乘上}\left( \begin{array}{c} n\\ i\\ \end{array} \right) i!\text{后即为}\prod_{1\le i\le L}{\left( \sum_{1\le j\le N}{f\left( j,i \right)} \right) ^F}\text{展开后存在}i\text{个无关变量的项的系数和。} \\ \text{显然只有不超过}L\times F\text{个无关变量的项才是有意义的}. \\ \text{复杂度}O\left( LF\log LF\times \log L \right) \\ \text{注意}F=\text{1的时候需要特判,否则太慢了跑不过去。} \\ $$

    #include <assert.h>
    #include <algorithm>
    #include <cmath>
    // #include <complex>
    #include <cstring>
    #include <iostream>
    using real_t = double;
    
    using int_t = long long int;
    using std::cin;
    using std::cout;
    using std::endl;
    using cpx_t = struct complex;
    
    struct complex {
        real_t real, imag;
        complex(real_t real = 0, real_t imag = 0) {
            this->real = real;
            this->imag = imag;
        }
        complex operator+(const complex& rhs) const {
            return complex{real + rhs.real, imag + rhs.imag};
        }
        complex operator-(const complex& rhs) const {
            return complex{real - rhs.real, imag - rhs.imag};
        }
        complex operator*(const complex& rhs) const {
            return complex{real * rhs.real - imag * rhs.imag,
                           real * rhs.imag + imag * rhs.real};
        }
        complex& operator*=(const complex& rhs) {
            *this = (*this) * rhs;
            return *this;
        }
    };
    
    const int_t LARGE = 300000;
    const int_t mod = 2333;
    int_t S2[mod][mod];
    int_t fact[LARGE + 1], factInv[LARGE + 1], inv[LARGE + 1];
    const real_t PI = acosl(-1);
    int_t flips[LARGE + 1];
    void transform(cpx_t* A, int_t size2, int_t arg) {
        for (int_t i = 0; i < (1 << size2); i++) {
            int_t x = flips[i];
            if (x > i) std::swap(A[i], A[x]);
        }
        for (int i = 2; i <= (1 << size2); i *= 2) {
            auto mr = cpx_t(cos(2 * PI / i), arg * sin(2 * PI / i));
            for (int j = 0; j < (1 << size2); j += i) {
                auto curr = cpx_t(1, 0);
                for (int k = 0; k < i / 2; k++) {
                    auto u = A[j + k], t = A[j + k + i / 2] * curr;
                    A[j + k] = u + t;
                    A[j + k + i / 2] = u - t;
                    curr *= mr;
                }
            }
        }
    }
    void poly_mul(const int_t* Ax, const int_t* Bx, int_t size2, int_t n,
                  int_t* Cx) {
        static cpx_t A[LARGE * 2], B[LARGE * 2];
        for (int_t i = 0; i < (1 << size2); i++) {
            if (i < n) {
                A[i] = Ax[i];
                B[i] = Bx[i];
            } else {
                A[i] = B[i] = 0;
            }
        }
        transform(A, size2, 1);
        transform(B, size2, 1);
        for (int i = 0; i < (1 << size2); i++) A[i] *= B[i];
        transform(A, size2, -1);
        for (int_t i = 0; i < n; i++) {
            Cx[i] = (int_t((A[i]).real / (1 << size2) + 0.5) % mod);
            assert(Cx[i] >= 0);
        }
    }
    int_t process() {
        static int_t A[LARGE + 1], B[LARGE + 1];
    
        int_t n, k, l, f;
        cin >> n >> k >> l >> f;
        int_t size2 = 0;
        int_t nx = l * f + 1;
        while ((1 << size2) < (nx * 2)) size2++;
        for (int_t i = 0; i < (1 << size2); i++) {
            const auto flip = [&](int_t x) {
                int_t result = 0;
                for (int_t i = 0; i < size2 - 1; i++) {
                    result |= (x & 1);
                    x >>= 1;
                    result <<= 1;
                }
                return result | (x & 1);
            };
            flips[i] = flip(i);
        }
        std::fill(A, A + (1 << size2), 0);
        std::fill(B, B + (1 << size2), 0);
    
        if (k % mod == 0) return 0;
        for (int_t i = 1; i <= f; i++) {
            A[i] = S2[f][i];
        }
        B[0] = 1;
    #ifdef DEBUG
        cout << "size2 = " << size2 << " len = " << (1 << size2) << endl;
        cout << "nx  = " << nx << endl;
        cout << "A = ";
        for (int_t i = 0; i <= f; i++) cout << A[i] << " ";
        cout << endl;
    
    #endif
    
        if (f == 1) {
            B[l] = 1;
        } else {
            while (l) {
                assert(l >= 0);
                if (l & 1) poly_mul(A, B, size2, nx, B);
                poly_mul(A, A, size2, nx, A);
                l >>= 1;
            }
        }
    #ifdef DEBUG
        cout << "mul ok B = ";
        for (int_t i = 0; i < nx; i += 1) cout << B[i] << " ";
        cout << endl;
    
    #endif
        int_t result = 0;
        int_t kinv = inv[k % mod];
        int_t under = kinv;
        int_t prod = n;
        for (int_t i = 1; i < nx; i++) {
            result = (result + B[i] % mod * under * prod % mod) % mod;
    #ifdef DEBUG
            cout << "at " << i << " count on " << B[i] * under * prod % mod << endl;
    #endif
            under = under * kinv % mod;
            prod = prod * (n - i) % mod;
            if (n <= i) break;
        }
        return result;
    }
    int main() {
        S2[0][0] = 1;
        for (int_t i = 1; i < mod; i++) {
            for (int_t j = 1; j <= i; j++) {
                S2[i][j] = (S2[i - 1][j - 1] + S2[i - 1][j] * j) % mod;
            }
        }
        fact[0] = fact[1] = factInv[0] = factInv[1] = inv[1] = 1;
        for (int_t i = 2; i < mod; i++) {
            inv[i] = (mod - mod / i) * inv[mod % i] % mod;
            factInv[i] = factInv[i - 1] * inv[i] % mod;
            fact[i] = fact[i - 1] * i % mod;
        }
        int_t T;
        cin >> T;
        while (T--) cout << process() << endl;
    
        return 0;
    }

     

  • noi.13185 game

    真心送分题。

    没救了。

    以后写题之前一定好好想想这样做对不对。

    分情况讨论。

    Bob手里有至少一张比Alice所有牌都小的牌

    由于Alice不傻,它一定会把除了这张牌之外的牌全扔掉,最后赢了。

    Bob手里不存在一张比Alice手里所有牌都小的牌,但是存在至少一张不大于Alice手里任何牌的牌

    除非Bob傻,否则Alice现在一定赢不了了。

    但是Alice可以把除了这张牌之外的牌全部扔掉,Bob也会把比这张牌大的牌(如果存在的话)扔掉,得到平局

    除此之外的情况

    Bob赢了。

    #include <algorithm>
    #include <cstring>
    #include <iostream>
    #include <set>
    #include <utility>
    using pair_t = std::pair<int, int>;
    using int_t = long long int;
    using std::cin;
    using std::cout;
    using std::endl;
    pair_t rev(pair_t x) { return pair_t(x.second, x.first); }
    std::ostream& operator<<(std::ostream& os, pair_t x) {
        os << "{" << x.first << "," << x.second << "}";
        return os;
    }
    const int_t LARGE = 2e5;
    const char* process() {
        static int_t deg[2][LARGE + 1];
        memset(deg, 0, sizeof(deg));
        int_t n, m;
        scanf("%lld%lld", &n, &m);
        for (int_t i = 1; i <= m; i++) {
            int_t a, b;
            char buf[5];
            scanf("%lld%s%lld", &a, buf, &b);
            if (buf[0] == '<')
                deg[1][b]++;
            else
                deg[0][b]++;
        }
        for (int_t i = 1; i <= n; i++)
            if (deg[0][i] == n) return "Alice";
        for (int_t i = 1; i <= n; i++)
            if (deg[1][i] == 0) return "Draw";
        return "Bob";
        //   return "";
    }
    
    int main() {
        int_t T;
        cin >> T;
        while (T--) cout << process() << endl;
    
        return 0;
    }

     

  • noi.ac 152 Christmas

    提高组DP吧。

    设$f(n,l,r)$表示从起点出发走到了第n列,并且在这一列的第l行到第r行可以自由走动的最小代价。

    我们对于每一个l,按照从小到大的顺序枚举r,然后先计算出所有区间[l,r]的答案,然后再把一个区间的答案拿去更新它的子区间。

    对于第n列的区间$[l,r]$,如果任意一个位置能被第n+1列的某个棋子控制,那么这个状态不合法。

    同理如果这个区间不被任意一个第n-1列的棋子控制,那么$f(n,l,r)$的答案可以从$f(n-1,k,k)$,其中$k\in [l,r]$转移而来。

    如果被至少一个棋子控制,那么设x为棋子行编号的最小值,y为棋子行标号的最大值,显然我们要保证第n-1列[x,y]之间的区域能自由走动,故可以从$f(n-1,x,y)$转移而来。

    但是有一个特例,如果第n列区间$[l,r]$被第n-1列l-1行的棋子控制住,那么我们不能从$f(n-1,l-1,l-1)$转移而来(否则跳过了第n-1列第l行这个棋子,直接飞过来的?),应该从$f(n-1,l-1,l)$转移而来。

    计算完第n列所有区间的答案后,再跑一遍朴素区间DP,把大区间的答案传递给他所有的子区间即可。

    边界条件:

    初始时所有DP值为INF,$f(0,n,n)=0$。

    #include <algorithm>
    #include <cstring>
    #include <iostream>
    using int_t = long long int;
    using std::cin;
    using std::cout;
    using std::endl;
    // f(n,l,r) 前n列,让纵坐标区间[l,r]可自由走动的最小代价
    int_t dp[1001][101][101];
    int_t mat[101][1001];
    int_t n, m;
    const int_t INF = 0x7fffffff;
    int main() {
        scanf("%lld%lld", &n, &m);
        for (int_t i = 1; i <= n; i++) {
            static char buf[10001];
            scanf("%s", buf + 1);
            for (int_t j = 1; j <= m; j++) {
                mat[i][j] = buf[j] - '0';
            }
        }
        memset(dp, 0x3f, sizeof(dp));
        //当前位置左上是否有东西
        const auto blockByLeft = [&](int_t r, int_t c) {
            r -= 1, c -= 1;
            if (r >= 1 && r <= n && c >= 1 && c <= m) return (bool)mat[r][c];
            return false;
        };
        const auto blockByRight = [&](int_t r, int_t c) {
            r -= 1, c += 1;
            if (r >= 1 && r <= n && c >= 1 && c <= m) return (bool)mat[r][c];
            return false;
        };
        if (blockByRight(n, 1)) {
            cout << -1 << endl;
            return 0;
        }
        dp[0][n][n] = 0;
        for (int_t i = 1; i <= m; i++) {
            //枚举上端点
            for (int_t j = 1; j <= n; j++) {
                int_t sum = 0;
                //左侧列屏蔽当前点的最大最小行编号
                int_t minblock = INF, maxblock = 0;
                int_t minval = INF;
                bool hasblock = false;
                for (int_t k = j; k <= n; k++) {
                    minval = std::min(minval, dp[i - 1][k][k]);
                    if (blockByRight(k, i)) break;
                    sum += mat[k][i];
                    if (blockByLeft(k, i)) {
                        minblock = std::min(minblock, k - 1);
                        maxblock = std::max(maxblock, k - 1);
                        hasblock = true;
                    }
                    if (!hasblock) {
                        dp[i][j][k] = std::min(dp[i][j][k], minval + sum);
                    } else {
                        dp[i][j][k] = std::min(
                            dp[i][j][k],
                            sum + dp[i - 1][minblock][std::max(j, maxblock)]);
                    }
                }
            }
            //长度
            for (int_t j = n; j >= 1; j--) {
                //左端点
                for (int_t k = 1; j + k - 1 <= n; k++) {
                    dp[i][k + 1][k + j - 1] =
                        std::min(dp[i][k + 1][k + j - 1], dp[i][k][k + j - 1]);
                    dp[i][k][k + j - 2] =
                        std::min(dp[i][k][k + j - 2], dp[i][k][k + j - 1]);
                }
            }
        }
        int_t result = INF;
        for (int_t i = 1; i <= n; i++) {
            for (int_t j = i; j <= n; j++) {
                result = std::min(result, dp[m][i][j]);
            }
        }
        if (result > n * m) {
            result = -1;
        }
        cout << result << endl;
    
        return 0;
    }

     

  • UOJ455 雪灾与外卖

    #include <algorithm>
    #include <iostream>
    #include <numeric>
    #include <queue>
    using int_t = long long int;
    using std::cin;
    using std::cout;
    using std::endl;
    
    const int_t LARGE = 1e5;
    const int_t INF = 0x7fffffffffffff;
    
    struct State {
        int_t value, count;
        bool operator<(const State& x) const { return value > x.value; }
    };
    int_t n, m;
    
    // heap1 送餐员堆 存放这个堆匹配左边餐厅时应该统计起来的权值
    // count表示同样权值的送餐员有多少个 heap2 餐厅堆
    // 存放这里面匹配左边送餐员时应该加的权值 count表示这个餐厅还能匹配多少个送餐员
    
    std::priority_queue<State> heap1, heap2;
    int_t xs[LARGE + 1], ys[LARGE + 1];
    int_t cost[LARGE + 1], count[LARGE + 1];
    int_t result = 0;
    //加入送餐员
    void pushA(int_t x) {
        //拿出一个餐厅并且把可用数量减1
        State next = heap2.top();
        heap2.pop();
        next.count--;
        //送餐员坐标为x,餐厅坐标为y,找到-y+w最小的餐厅与x配对
        result += x + next.value;
        //把这个送餐员加到堆里
        // count表示有多少个同种送餐员待匹配
        //这个送餐员的反悔操作(用于配对右边的餐厅)
        heap1.push(State{-(x + next.value) - x, 1});
        //把餐厅扔回去
        if (next.count) {
            heap2.push(next);
        }
    }
    //加入餐厅
    void pushB(int_t y, int_t cost, int_t count) {
        //当前餐厅已经配对的送餐员个数
        int_t used = 0;
        //当前餐厅匹配右边的送餐员,价值cost+y+val,其中val为堆顶
        //送餐员堆顶匹配当前餐厅更优
        while (heap1.empty() == false && used < count &&
               heap1.top().value + cost + y < 0) {
            auto next = heap1.top();
            heap1.pop();
            //可匹配数
            int_t oks = std::min(count - used, next.count);
            next.count -= oks;
            used += oks;
            //更新代价
            result += oks * (cost + y + next.value);
            //这个送餐员还可以匹配
            if (next.count > 0) heap1.push(next);
            //餐厅的反悔操作,这个操作用于匹配右边的送餐员
            //这次产生的代价为val+cost+y,故反悔操作的代价为-(val+cost+y) -y+cost
            //这个反悔操作每用上一个就少了一个这次的匹配
            //大概相当于建了个假餐厅?
            heap2.push(State{-(next.value + cost + y) - y + cost, oks});
        }
        //本次操作产生的代价为cost+y+next.value,故反悔操作代价为-(cost+y)
        //匹配过,把送餐员的反悔操作扔进堆里(所有操作代价都一样故统一扔)
    
        if (used) heap1.push(State{-(cost + y), used});
        //餐厅还剩下,扔回去
        if (count - used) heap2.push(State{-y + cost, count - used});
    }
    
    int main() {
        scanf("%lld%lld", &n, &m);
        for (int_t i = 1; i <= n; i++) scanf("%lld", &xs[i]);
        int_t sum = 0;
        for (int_t i = 1; i <= m; i++) {
            scanf("%lld%lld%lld", &ys[i], &cost[i], &count[i]);
            sum += count[i];
        }
        if (sum < n) {
            printf("-1");
            return 0;
        }
        // heap2.push(State{-INF, INF});
        pushB(-INF, 0, INF);
        int_t i = 1, j = 1;
        while (i <= n && j <= m) {
            //坐标小的往前推进 
            if (xs[i] <= ys[j]) {
                pushA(xs[i]);
                i++;
            } else {
                pushB(ys[j], cost[j], count[j]);
                j++;
            }
        }
        while (i <= n) pushA(xs[i++]);
    
        while (j <= m) {
            pushB(ys[j], cost[j], count[j]);
            j++;
        }
        cout << result << endl;
        return 0;
    }

     

  • SDOI2013 森林

    主席树启发式合并。

    一开始使用树剖LCA没发现出问题了,后来拍了半天才发现,然后换成了倍增。

    当然也可以用LCT维护LCA。

    #include <assert.h>
    #include <algorithm>
    #include <iostream>
    #include <vector>
    using int_t = int;
    using std::cin;
    using std::cout;
    using std::endl;
    const int_t LARGE = 1e5;
    void* allocate();
    struct Node {
        int begin, end;
        Node *left = nullptr, *right = nullptr;
        int value;
        Node(int_t begin, int_t end) {
            this->begin = begin;
            this->end = end;
            value = 0;
        }
        int_t query(int_t begin, int_t end) {
            if (end < this->begin || begin > this->end) return 0;
            if (this->begin >= begin && this->end <= end) return this->value;
            return left->query(begin, end) + right->query(begin, end);
        }
        void maintain() {
            if (begin != end) {
                this->value = left->value + right->value;
            }
        }
        static Node* build(int begin, int end) {
            Node* node = new Node(begin, end);
            if (begin != end) {
                int mid = (begin + end) / 2;
                node->left = build(begin, mid);
                node->right = build(mid + 1, end);
            }
            return node;
        }
    };
    int kth(int k, Node* v1, Node* v2, Node* lca, Node* lcap) {
        if (v1->begin == v1->end) return v1->begin;
        int_t leftVal = v1->left->value + v2->left->value - lca->left->value -
                        lcap->left->value;
        if (k <= leftVal)
            return kth(k, v1->left, v2->left, lca->left, lcap->left);
        else
            return kth(k - leftVal, v1->right, v2->right, lca->right, lcap->right);
    }
    Node* buildNext(Node* base, int_t pos, int_t val = 1) {
        Node* next = new (allocate()) Node(*base);
        if (next->begin == next->end) {
            next->value += 1;
        } else {
            int_t mid = (base->begin + base->end) / 2;
            if (pos <= mid)
                next->left = buildNext(base->left, pos, val);
            else
                next->right = buildNext(base->right, pos, val);
            next->maintain();
        }
        return next;
    }
    
    int depth[LARGE + 1], parent[LARGE + 1];
    int vals[LARGE + 1];
    int n, m, t;
    int jumpto[21][LARGE + 1];
    Node* roots[LARGE + 1];
    std::vector<int_t> graph[LARGE + 1];
    const int_t SIZE = LARGE * 200;
    char buf[SIZE * sizeof(Node)];
    int_t used = 0;
    void* allocate() {
        assert(used < SIZE);
        return buf + (used++) * sizeof(Node);
    }
    
    namespace UFS {
    int_t size[LARGE + 1], parent[LARGE + 1];
    void init() {
        for (int i = 1; i <= n; i++) {
            size[i] = 1;
            parent[i] = i;
        }
    }
    int query(int x) {
        while (parent[x] != x) x = parent[x];
        return x;
    }
    void merge(int a, int b) {
        if (size[a] > size[b]) std::swap(a, b);
        a = query(a);
        b = query(b);
        if (a == b) return;
        size[b] += size[a];
        parent[a] = b;
    }
    }  // namespace UFS
    
    void DFS1(int_t vtx, Node* base, int_t from = -1) {
        roots[vtx] = buildNext(base, vals[vtx], 1);
        parent[vtx] = from;
        if (from <= 0)
            jumpto[0][vtx] = -1;
        else
            jumpto[0][vtx] = from;
        for (int_t to : graph[vtx]) {
            if (to != from) {
                depth[to] = depth[vtx] + 1;
                DFS1(to, roots[vtx], vtx);
            }
        }
    }
    void DFS2(int vtx, int from, int k) {
        if ((1 << k) > depth[vtx])
            jumpto[k][vtx] = -1;
        else {
            jumpto[k][vtx] = jumpto[k - 1][jumpto[k - 1][vtx]];
        }
        for (auto to : graph[vtx]) {
            if (to != from) DFS2(to, vtx, k);
        }
    }
    void link(int v1, int v2) {
        if (UFS::size[UFS::query(v1)] > UFS::size[UFS::query(v2)])
            std::swap(v1, v2);
    
        graph[v1].push_back(v2);
        graph[v2].push_back(v1);
        depth[v1] = depth[v2] + 1;
    
        DFS1(v1, roots[v2], v2);
        for (int i = 1; i <= 18; i++) DFS2(v1, v2, i);
        UFS::merge(v1, v2);
    
    }
    int queryLCA(int v1, int v2) {
        if (depth[v1] < depth[v2]) std::swap(v1, v2);
        int diff = depth[v1] - depth[v2];
        for (int i = 18; i >= 0; i--) {
            if (diff & (1 << i)) v1 = jumpto[i][v1];
        }
        if (v1 == v2) return v1;
        assert(depth[v1] == depth[v2]);
        for (int i = 18; i >= 0; i--) {
            if (jumpto[i][v1] != jumpto[i][v2]) {
                v1 = jumpto[i][v1];
                v2 = jumpto[i][v2];
            }
        }
        assert(parent[v1] == parent[v2]);
        return parent[v1];
    }
    std::vector<int> numbers;
    int query(int v1, int v2, int k) {
        if (v1 > n || v2 > n) return 0;
        if (UFS::query(v1) != UFS::query(v2)) return 0;
        auto lca = queryLCA(v1, v2);
    
        if (parent[lca] == -1)
            return kth(k, roots[v1], roots[v2], roots[lca], roots[0]);
        return kth(k, roots[v1], roots[v2], roots[lca], roots[parent[lca]]);
    }
    int getRank(int x) {
        return std::lower_bound(numbers.begin(), numbers.end(), x) -
               numbers.begin() + 1;
    }
    void process() {
        used = 0;
        scanf("%d%d%d", &n, &m, &t);
        numbers.clear();
        for (int i = 1; i <= n; i++) {
            scanf("%d", &vals[i]);
            numbers.push_back(vals[i]);
        }
        UFS::init();
        std::sort(numbers.begin(), numbers.end());
        numbers.resize(std::unique(numbers.begin(), numbers.end()) -
                       numbers.begin());
        for (int i = 1; i <= n; i++) {
            vals[i] = getRank(vals[i]);
        }
        for (int i = 1; i <= m; i++) {
            int v1, v2;
            scanf("%d%d", &v1, &v2);
            graph[v1].push_back(v2);
            graph[v2].push_back(v1);
            UFS::merge(v1, v2);
        }
        for (int i = 1; i <= n; i++)
            if (roots[i] == nullptr) {
                DFS1(i, roots[0]);
                //枚举顺序写错了!!
                for (int j = 1; j <= 18; j++) DFS2(i, -1, j);
            }
        int lastans = 0;
        for (int i = 1; i <= t; i++) {
            char buf[5];
            scanf("%s", buf);
            if (buf[0] == 'L') {
                int v1, v2;
                scanf("%d%d", &v1, &v2);
                v1 ^= lastans;
                v2 ^= lastans;
    
                link(v1, v2);
            } else {
                int v1, v2, k;
                scanf("%d%d%d", &v1, &v2, &k);
                v1 ^= lastans;
                v2 ^= lastans;
                k ^= lastans;
                int_t result = query(v1, v2, k);
                if (result == 80001) {
                    cout << 0 << endl;
                    continue;
                }
                lastans = numbers[result - 1];
                printf("%d\n", lastans);
            }
        }
    }
    int main() {
        roots[0] = Node::build(1, 8e4 + 1);
        int T;
        cin >> T;
        process();
        return 0;
    }

     

  • 洛谷4705 玩游戏

    $$ \text{原式}=\frac{1}{nm}\sum_{1\le k\le t}{x^k\sum_{1\le i\le n}{\sum_{1\le j\le m}{\left( a_i+b_j \right) ^k}}} \\ =\frac{1}{nm}\sum_{1\le k\le t}{x^k\sum_{1\le i\le n}{\sum_{1\le j\le m}{\sum_{0\le y\le k}{\left( \begin{array}{c} k\\ y\\ \end{array} \right) a_{i}^{y}b_{j}^{k-y}}}}} \\ =\frac{1}{nm}\sum_{1\le k\le t}{x^kk!\sum_{0\le y\le k}{\frac{\sum_{1\le i\le n}{a_{i}^{y}}}{y!}\times \frac{\sum_{1\le j\le m}{b_{j}^{k-y}}}{\left( k-y \right) !}}} \\ \\ \text{考虑如何计算 }\sum_{1\le i\le n}{a_{i}^{y}} \\ \text{令}F\left( x \right) =\prod_{1\le i\le n}{\left( 1+a_ix \right)},\text{显然}F\left( x \right) \text{可以在}O\left( n\log ^2n \right) \text{的时间内计算出。} \\ \ln F\left( x \right) =\sum_{1\le i\le n}{\ln \left( 1+a_ix \right)},\text{把}\ln \left( x \right) \text{在}x=\text{1处展开可得} \\ \ln F\left( x \right) =\sum_{1\le i\le n}{\sum_{j\ge 1}{\frac{\left( -1 \right) ^{j-1}}{j}\left( a_ix \right) ^j}} \\ =\sum_{j\ge 1}{x^j\frac{\left( -1 \right) ^{j-1}}{j}\sum_{1\le i\le n}{a_i^j}} \\ \text{即}i\text{次项前的系数除以}\frac{\left( -1 \right) ^{i-1}}{i}\text{即}i\text{次幂的和}. $$

    #include <cstring>
    #include <iostream>
    #include <vector>
    using int_t = long long int;
    using std::cin;
    using std::cout;
    using std::endl;
    
    const int_t mod = 998244353;
    const int_t g = 3;
    const int_t LARGE = (1 << 20);
    
    void transform(int_t* A, int_t size2, int_t arg);
    void poly_inv(const int_t* A, int_t n, int_t* result);
    std::vector<int> process(const std::vector<int>& vec);
    void poly_log(const int_t* A, int_t n, int_t* result);
    int_t power(int_t base, int_t index) {
        const auto phi = mod - 1;
        if (index < 0) index = (index % phi + phi) % phi;
        int_t result = 1;
        while (index) {
            if (index & 1) result = result * base % mod;
            index >>= 1;
            base = base * base % mod;
        }
        return result;
    }
    std::vector<int> process(const std::vector<int>& vec) {
        if (vec.size() == 1) {
            return std::vector<int>{1, vec[0]};
        }
        std::vector<int> left, right;
        for (int i = 0; i < vec.size(); i++) {
            if (i < vec.size() / 2)
                left.push_back(vec[i]);
            else
                right.push_back(vec[i]);
        }
        auto leftRet = process(std::move(left));
        auto rightRet = process(std::move(right));
        static int_t A[LARGE], B[LARGE];
        int size2 = 0;
        while ((1 << size2) < leftRet.size() + rightRet.size()) size2++;
        std::copy(leftRet.begin(), leftRet.end(), A);
        std::copy(rightRet.begin(), rightRet.end(), B);
        transform(A, size2, 1);
        transform(B, size2, 1);
        for (int i = 0; i < (1 << size2); i++) A[i] = A[i] * B[i] % mod;
        transform(A, size2, -1);
        const auto inv = power(1 << size2, -1);
        std::vector<int> result;
        for (int i = 0; i < leftRet.size() + rightRet.size() - 1; i++)
            result.push_back(A[i] * inv % mod);
        std::fill(A, A + (1 << size2), 0);
        std::fill(B, B + (1 << size2), 0);
        return result;
    }
    int main() {
        std::vector<int> polyA;
        std::vector<int> polyB;
        static int_t fact[LARGE + 1], factInv[LARGE + 1];
        int n, m, t;
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++) {
            polyA.push_back(0);
            scanf("%d", &polyA.back());
        }
        for (int i = 1; i <= m; i++) {
            polyB.push_back(0);
            scanf("%d", &polyB.back());
        }
        scanf("%d", &t);
        fact[0] = factInv[0] = fact[1] = factInv[1] = 1;
        for (int_t i = 2; i <= LARGE; i++) {
            fact[i] = fact[i - 1] * i % mod;
            factInv[i] = (mod - mod / i) * factInv[mod % i] % mod;
        }
        for (int_t i = 2; i <= LARGE; i++)
            factInv[i] = factInv[i - 1] * factInv[i] % mod;
        const auto retA = process(polyA);
        const auto retB = process(polyB);
        static int_t A[LARGE], B[LARGE], Ax[LARGE], Bx[LARGE];
        std::copy(retA.begin(), retA.end(), A);
        std::copy(retB.begin(), retB.end(), B);
        poly_log(A, t + 1, Ax);
        poly_log(B, t + 1, Bx);
        memset(A, 0, sizeof(A));
        memset(B, 0, sizeof(B));
        A[0] = n;
        B[0] = m;
        for (int i = 1; i <= t; i++) {
            A[i] =
                Ax[i] * i % mod * ((i % 2) ? 1 : mod - 1) % mod * factInv[i] % mod;
            B[i] =
                Bx[i] * i % mod * ((i % 2) ? 1 : mod - 1) % mod * factInv[i] % mod;
        }
        int size2 = 0, size = 1;
        while ((1 << size2) < 2 * t) size2++;
        size = (1 << size2);
        transform(A, size2, 1);
        transform(B, size2, 1);
        for (int i = 0; i < size; i++) A[i] = A[i] * B[i] % mod;
        transform(A, size2, -1);
        const auto inv = power((int_t)size * n % mod * m % mod, -1);
        for (int i = 1; i <= t; i++) {
            printf("%lld\n", A[i] * inv % mod * fact[i] % mod);
        }
    
        return 0;
    }
    
    void poly_log(const int_t* A, int_t n, int_t* result) {
        int_t size = 1, size2 = 0;
        while ((1 << size2) < n * 2) size2++;
        size = (1 << size2);
        static int_t under[LARGE + 1], Ax[LARGE + 1];
        for (int_t i = 0; i < size; i++) {
            if (i < n)
                Ax[i] = A[i + 1] * (i + 1) % mod;
            else
                under[i] = Ax[i] = 0;
            under[i] = 0;
        }
        poly_inv(A, n, under);
        transform(under, size2, 1);
        transform(Ax, size2, 1);
        for (int i = 0; i < size; i++) Ax[i] = Ax[i] * under[i] % mod;
        transform(Ax, size2, -1);
        const int_t inv = power(size, -1);
        for (int i = 1; i < n; i++)
            result[i] = Ax[i - 1] * power(i, -1) % mod * inv % mod;
        result[0] = 0;
    }
    // C(x)=2B(x)-B(x)^2A(x)
    void poly_inv(const int_t* A, int_t n, int_t* result) {
        if (n == 1) {
            result[0] = power(A[0], -1);
            return;
        }
        poly_inv(A, n / 2 + n % 2, result);
        static int_t Ax[LARGE + 1];
        int_t size2 = 0;
        while ((1 << size2) < 3 * n) size2++;
        for (int_t i = 0; i < (1 << size2); i++) {
            if (i < n)
                Ax[i] = A[i];
            else
                Ax[i] = 0;
        }
    
        transform(Ax, size2, 1);
        transform(result, size2, 1);
        for (int_t i = 0; i < (1 << size2); i++)
            result[i] = (2 * result[i] % mod -
                         result[i] * result[i] % mod * Ax[i] % mod + mod) %
                        mod;
        transform(result, size2, -1);
        const int_t inv = power(1 << size2, -1);
        for (int_t i = 0; i < (1 << size2); i++)
            if (i < n)
                result[i] = result[i] * inv % mod;
            else
                result[i] = 0;
    }
    
    void transform(int_t* A, int_t size2, int_t arg) {
        const auto bitRev = [&](int_t x) {
            int_t result = 0;
            for (int_t i = 1; i < size2; i++) {
                result |= (x & 1);
                result <<= 1;
                x >>= 1;
            }
            return result | (x & 1);
        };
        for (int_t i = 0; i < (1 << size2); i++) {
            int_t x = bitRev(i);
            if (x > i) std::swap(A[i], A[x]);
        }
        for (int_t i = 2; i <= (1 << size2); i *= 2) {
            int_t mr = power(g, (mod - 1) / i * arg);
            for (int_t j = 0; j < (1 << size2); j += i) {
                int_t curr = 1;
                for (int_t k = 0; k < i / 2; k++) {
                    int_t u = A[j + k], t = A[j + k + i / 2] * curr % mod;
                    A[j + k] = (u + t) % mod;
                    A[j + k + i / 2] = (u - t + mod) % mod;
                    curr = curr * mr % mod;
                }
            }
        }
    }

     

  • POI2011 Meteors

    整体二分板子题。

    自己还能YY点东西…

    大概就是递归下去二分。

    每次对于一个二分区间$[left,right]$,设中点为$mid$,那么把在$[left,mid]$范围内满足条件的询问扔到一起,把满足不了条件的询问也扔到一起,然后分别塞给$[left,mid]$区间和$[mid+1,right]$区间。

    然后递归下去即可。

    对于这题复杂度很显然是$O(n\log ^2 )$。

    考虑二分的每一层都有$O(n)$个询问要处理,这些询问全部check一遍(使用树状数组)的复杂度是$O(n\log  n)$的。

    #include <assert.h>
    #include <iostream>
    #include <vector>
    using int_t = long long int;
    using std::cin;
    using std::cout;
    using std::endl;
    
    const int_t LARGE = 3e5 + 10;
    int_t barr[LARGE + 1];
    int n, m, k;
    
    struct Opt {
        int left, right, val;
    } opts[LARGE + 1];
    std::vector<int> stations[LARGE + 1];
    //某个国家的期望值
    int req[LARGE + 1];
    //整体二分使用的数组
    int arr[LARGE + 1];
    int result[LARGE + 1];
    int lowbit(int x) { return x & -x; }
    int_t query(int x) {
        int_t result = 0;
        while (x >= 1) {
            result += barr[x];
            x -= lowbit(x);
        }
        return result;
    }
    void addPos(int x, int val) {
        while (x <= m) {
            barr[x] += val;
            x += lowbit(x);
        }
    }
    void add(int left, int right, int val) {
        addPos(left, val);
        addPos(right + 1, -val);
    }
    //执行操作区间
    void execute(int left, int right, int arg = 1) {
        for (int i = left; i <= right; i++) {
            const auto& curr = opts[i];
            if (curr.left <= curr.right)
                add(curr.left, curr.right, curr.val * arg);
            else {
                add(curr.left, m, curr.val * arg);
                add(1, curr.right, curr.val * arg);
            }
        }
    }
    //[left,right] 当前二分的答案区间
    //[cleft,cright] 当前二分的答案区间对应的询问
    void process(int left, int right, int offset, int* cleft, int* cright) {
        //没有询问,没必要递归
        if (cleft > cright) return;
        int mid = (left + right) / 2;
        //应用mid及之前的操作
        execute(offset, mid, 1);
        static int oks[LARGE + 1], notOks[LARGE + 1];
        oks[0] = notOks[0] = 0;
        for (auto ptr = cleft; ptr <= cright; ptr++) {
            __int128_t count = 0;
            for (auto x : stations[*ptr]) count += query(x);
            if (count >= req[*ptr])
                oks[++oks[0]] = *ptr;
            else
                notOks[++notOks[0]] = *ptr;
        }
        auto ptr = cleft;
        for (int i = 1; i <= oks[0]; i++) *(ptr++) = oks[i];
        auto midptr = ptr - 1;
        for (int i = 1; i <= notOks[0]; i++) *(ptr++) = notOks[i];
        assert(ptr == cright + 1);
        // //递归终点,统计答案
        if (left == right) {
            //没有撤销!!
            execute(offset, mid, -1);
            for (int i = 1; i <= oks[0]; i++) result[oks[i]] = left;
            for (auto i = 1; i <= notOks[0]; i++) result[notOks[i]] = -1;
            return;
        }
        assert(offset == left);
        process(mid + 1, right, mid + 1, midptr + 1, cright);
        execute(offset, mid, -1);
        process(left, mid, offset, cleft, midptr);
    }
    int main() {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= m; i++) {
            int x;
            scanf("%d", &x);
            stations[x].push_back(i);
        }
        for (int i = 1; i <= n; i++) {
            arr[i] = i;
            scanf("%d", &req[i]);
        }
        scanf("%d", &k);
        for (int i = 1; i <= k; i++) {
            auto& curr = opts[i];
            scanf("%d%d%d", &curr.left, &curr.right, &curr.val);
        }
        process(1, k, 1, &arr[1], &arr[n]);
        for (int i = 1; i <= n; i++) {
            if (result[i] != -1) {
                printf("%d\n", result[i]);
            } else {
                printf("NIE\n");
            }
        }
        return 0;
    }

     

  • POI2014 Hotels

    设$f(vtx,n)$表示vtx为根的子树内,到vtx距离为n的点的个数。

    设$g(vtx,n)$表示vtx内,如果在vtx再接上一个长度为u的链(目标为v)就能和v形成符合题目要求的三元组的点对个数。

    转移的时候枚举子节点,先统计答案,然后合并状态。

    对于边$(u,v)$,能贡献的答案为$g(u,x)\times f(v,x-1)+f(u,x)\times g(v,x+1)$。

    然后合并进去,$g(u,x)+=f(u,x)\times f(v,x-1)+g(v,x+1)$以及$f(u,x)+=f(v,x-1)$。

    注意要先在统计答案的同时合并状态,因为我们统计的是无序三元组。

    复杂度$O(n^3)$,优化一下到$O(n^2)$。

    考虑长链剖分优化。

    每个点的状态直接从重子节点拖过来,然后其他节点的答案参考上面暴力合并进去。

    #include <iostream>
    #include <vector>
    using int_t = long long int;
    using std::cin;
    using std::cout;
    using std::endl;
    
    const int_t LARGE = 2e5;
    int_t depth[LARGE + 1], maxChd[LARGE + 1], maxDepth[LARGE + 1];
    std::vector<int_t> graph[LARGE + 1];
    
    void DFS1(int_t vtx, int_t from = -1) {
        if (from == -1) depth[vtx] = 1;
        maxDepth[vtx] = depth[vtx];
        for (int_t to : graph[vtx]) {
            if (to != from) {
                depth[to] = depth[vtx] + 1;
                DFS1(to, vtx);
                maxDepth[vtx] = std::max(maxDepth[vtx], maxDepth[to]);
                if (maxDepth[to] > maxDepth[maxChd[vtx]]) maxChd[vtx] = to;
            }
        }
    }
    // f(vtx,u)表示vtx子树内到vtx距离为u的点的个数
    // g(vtx,u)表示vtx内,如果在vtx再接上一个长度为u的链(目标为v)就能和v形成符合题目要求的三元组的点对个数
    void DFS2(int_t vtx, int_t from, int_t* f, int_t* g, int_t& result) {
        //递归重子节点
        if (maxChd[vtx]) {
            DFS2(maxChd[vtx], vtx, f + 1, g - 1, result);
    #ifdef DEBUG
            cout << "at vtx " << vtx << " done heavys" << endl;
            for (int_t i = 0; i <= maxDepth[vtx] - depth[vtx]; i++)
                cout << "f " << i << " = " << f[i] << endl;
            for (int_t i = 0; i <= maxDepth[vtx] - depth[vtx]; i++)
                cout << "g " << i << " = " << g[i] << endl;
            cout << endl;
    #endif
        }
        f[0] += 1;
        //重链贡献的答案
        result += g[0];
        //递归轻子节点
        for (int_t to : graph[vtx]) {
            if (to != from && to != maxChd[vtx]) {
                const int_t size = maxDepth[to] - depth[vtx] + 1;
                auto nextF = new int_t[size];
                auto nextG = new int_t[size * 2];
                std::fill(nextF, nextF + size, 0);
                std::fill(nextG, nextG + 2 * size, 0);
                nextG += size;
                DFS2(to, vtx, nextF, nextG, result);
                for (int_t i = 0; i < maxDepth[to] - depth[vtx]; i++) {
                    result += g[i + 1] * (nextF)[i];
                    if (i) result += (nextG)[i] * f[i - 1];
                }
                //合并答案
                for (int_t i = 0; i < maxDepth[to] - depth[vtx]; i++) {
                    //合并g
                    g[i + 1] += f[i + 1] * (nextF)[i];
                    if (i) g[i - 1] += (nextG)[i];
                    f[i + 1] += (nextF)[i];
                }
    #ifdef DEBUG
                cout << "at vtx " << vtx << " done chd " << to << endl;
                for (int_t i = 0; i <= maxDepth[vtx] - depth[vtx]; i++)
                    cout << "f " << i << " = " << f[i] << endl;
                for (int_t i = 0; i <= maxDepth[vtx] - depth[vtx]; i++)
                    cout << "g " << i << " = " << g[i] << endl;
                cout << endl;
    #endif
            }
        }
    #ifdef DEBUG
        cout << "at vtx " << vtx << endl;
        for (int_t i = 0; i <= maxDepth[vtx] - depth[vtx]; i++)
            cout << "f " << i << " = " << f[i] << endl;
        for (int_t i = 0; i <= maxDepth[vtx] - depth[vtx]; i++)
            cout << "g " << i << " = " << g[i] << endl;
        cout << endl;
    #endif
        // result += g[0];
    }
    int n;
    int main() {
        scanf("%d", &n);
        for (int i = 1; i <= n - 1; i++) {
            int from, to;
            scanf("%d%d", &from, &to);
            graph[from].push_back(to);
            graph[to].push_back(from);
        }
        DFS1(1);
    #ifdef DEBUG
        for (int_t i = 1; i <= n; i++) {
            cout << "maxChd " << i << " = " << maxChd[i] << endl;
        }
    #endif
        int_t result = 0;
        int_t size = n + 1;
        auto f = new int_t[size];
        auto g = new int_t[size * 2];
        std::fill(f, f + size, 0);
        std::fill(g, g + size * 2, 0);
        DFS2(1, -1, f, g + size, result);
        cout << result << endl;
        return 0;
    }