分类: 未分类

  • 常系数线性递推的新做法 – 计算[x^k]P(x)/Q(x)

    $$ a_n=\sum_{1\le i\le k}{f_ia_{n-i}} \\ \left\{ a_0,a_1….a_{k-1} \right\} \text{已知} \\ \\ \text{设}F\left( x \right) \text{表示从第}k\text{项开始的该数列的生成函数} \\ \sum_{i\ge k}{a_ix^i}=\sum_{i\ge k}{\sum_{1\le j\le k}{f_ja_{i-j}x^i}} \\ =\sum_{1\le j\le k}{f_j\sum_{i\ge k}{a_{i-j}x^i}} \\ =\sum_{1\le j\le k}{f_j\sum_{i\ge k-j}{a_ix^{i+j}}} \\ =\sum_{1\le j\le k}{f_jx^j\sum_{i\ge k-j}{a_ix^i}} \\ =\sum_{1\le j\le k}{f_jx^j\left( F\left( x \right) +\sum_{k-j\le i\le k-1}{a_ix^i} \right)} \\ \\ F\left( x \right) =F\left( x \right) \sum_{1\le j\le k}{f_jx^j}+\sum_{1\le j\le k}{f_jx^j\sum_{k-j\le i\le k-1}{a_ix^i}} \\ F\left( x \right) =\frac{\sum_{1\le j\le k}{f_jx^j\sum_{k-j\le i\le k-1}{a_ix^i}}}{1-\sum_{1\le j\le k}{f_jx^j}} \\ =\frac{\sum_{1\le j\le k}{f_jx^j\sum_{k-j\le i-j\le k-1}{a_{i-j}x^{i-j}}}}{1-\sum_{1\le j\le k}{f_jx^j}} \\ =\frac{\sum_{1\le j\le k}{f_j\sum_{k\le i\le k-1+j}{a_{i-j}x^i}}}{1-\sum_{1\le j\le k}{f_jx^j}} \\ =\frac{\sum_{k\le i\le 2k-1}{\begin{array}{c} x^i\sum_{i-k+1\le j\le k}{a_{i-j}f_j}\\ \end{array}}}{1-\sum_{1\le j\le k}{f_jx^j}} \\ F\left( x \right) +\sum_{0\le i\le k-1}{a_ix^i}=\frac{\left( 1-\sum_{1\le j\le k}{f_jx^j} \right) \left( \sum_{0\le i\le k-1}{a_ix^i} \right) +\sum_{1\le j\le k}{f_j\sum_{k\le i\le k-1+j}{a_{i-j}x^i}}}{1-\sum_{1\le j\le k}{f_jx^j}} \\ \frac{\sum_{0\le i\le k-1}{a_ix^i}-\sum_{1\le j\le k}{f_j\sum_{0\le i\le k-1}{a_ix^{i+j}}}+\sum_{1\le j\le k}{f_j\sum_{k\le i\le k-1+j}{a_{i-j}x^i}}}{1-\sum_{1\le j\le k}{f_jx^j}} \\ =\frac{\sum_{0\le i\le k-1}{a_ix^i}-\sum_{1\le j\le k}{f_j\sum_{j\le i\le j+k-1}{a_{i-j}x^i}}+\sum_{1\le j\le k}{f_j\sum_{k\le i\le k-1+j}{a_{i-j}x^i}}}{1-\sum_{1\le j\le k}{f_jx^j}} \\ \frac{\sum_{0\le i\le k-1}{a_ix^i}+\sum_{1\le j\le k}{f_j\left( \sum_{k\le i\le k-1+j}{a_{i-j}x^i}-\sum_{j\le i\le j+k-1}{a_{i-j}x^i} \right)}}{1-\sum_{1\le j\le k}{f_jx^j}} \\ \frac{\sum_{0\le i\le k-1}{a_ix^i}+\sum_{1\le j\le k}{f_j\left( \sum_{k\le i\le k-1+j}{a_{i-j}x^i}-\sum_{k\le i\le j+k-1}{a_{i-j}x^i}-\sum_{j\le i\le k-1}{a_{i-j}x^i} \right)}}{1-\sum_{1\le j\le k}{f_jx^j}} \\ \frac{\sum_{0\le i\le k-1}{a_ix^i}+\sum_{1\le j\le k}{f_j\left( -\sum_{j\le i\le k-1}{a_{i-j}x^i} \right)}}{1-\sum_{1\le j\le k}{f_jx^j}} \\ =\frac{\sum_{0\le i\le k-1}{a_ix^i}-\sum_{1\le j\le k}{f_j\left( \sum_{j\le i\le k-1}{a_{i-j}x^i} \right)}}{1-\sum_{1\le j\le k}{f_jx^j}} \\ =\frac{\sum_{0\le i\le k-1}{a_ix^i}-\sum_{1\le j\le k-1}{f_j\left( \sum_{j\le i\le k-1}{a_{i-j}x^i} \right)}}{1-\sum_{1\le j\le k}{f_jx^j}} \\ =\frac{\sum_{0\le i\le k-1}{a_ix^i}-\sum_{1\le i\le k-1}{x^i\sum_{1\le j\le i}{a_{i-j}f_j}}}{1-\sum_{1\le j\le k}{f_jx^j}} \\ =\frac{a_0+\sum_{1\le i\le k-1}{x^ia_i}-\sum_{1\le i\le k-1}{x^i\sum_{1\le j\le i}{a_{i-j}f_j}}}{1-\sum_{1\le j\le k}{f_jx^j}} \\ =\frac{a_0+\sum_{1\le i\le k-1}{x^i\left( a_i-\sum_{1\le j\le i}{a_{i-j}f_j} \right)}}{1-\sum_{1\le j\le k}{f_jx^j}} \\ \text{我们得到了原数列}\left\{ a_i \right\} \text{的生成函数}G\left( x \right) =\frac{P\left( x \right)}{Q\left( x \right)} \\ \text{考虑计算这个有理函数的}k\text{次项} \\ \text{令}G_k\left( x \right) =\frac{P\left( x \right)}{Q\left( x \right)}=\frac{P\left( x \right) P\left( -x \right)}{Q\left( x \right) Q\left( -x \right)}=\frac{xA\left( x^2 \right) +B\left( x^2 \right)}{C\left( x^2 \right)} \\ \text{即分母只有偶数次方项},\text{分子的奇数次方项和偶数次方项拆开} \\ \text{如果}k\text{为奇数},\text{那么}x^k\text{之可能存在于}x\frac{A\left( x^2 \right)}{C\left( x^2 \right)}\text{中}\left( \text{只有这边才有奇数次方} \right) \\ \text{同理},\text{如果}k\text{为偶数,那么}x^k\text{只能出现在}\frac{B\left( x^2 \right)}{C\left( x^2 \right)}\text{中,} \\ \text{然后根据}k\text{的奇偶性,继续递归} \\ \text{如果}k\text{是奇数},\text{那么计算}G_{\frac{k-1}{2}}\left( x \right) =\frac{A\left( x \right)}{C\left( x \right)}\left( \text{项的次数除以}2 \right) ,\text{答案为}\left[ x^{\frac{k-1}{2}} \right] G_{\frac{k-1}{2}}\left( x \right) \\ \text{如果}k\text{是偶数},\text{那么计算}G_{\frac{k}{2}}\left( x \right) =\frac{B\left( x \right)}{C\left( x \right)}\text{,答案为}\left[ x^k \right] G_{\frac{k}{2}}\left( x \right) \\ \text{然后有两种选择}:\text{递归到}k=0\text{时,计算}\frac{P\left( 0 \right)}{G\left( 0 \right)}\mathrm{mod}p\text{,此时不需要多项式求逆运算} \\ \text{但常数较大}\left( \text{递归次数多} \right) \\ \text{递归到}k<n\text{时,直接求逆计算。} \\ \\ \\ \\ \\ $$

    代码1: 递归到$k=0$时执行整数运算,较慢

    #include <cstring>
    #include <iostream>
    using int_t = long long int;
    using std::cin;
    using std::cout;
    using std::endl;
    
    const int_t LARGE = 2e6;
    
    const int_t mod = 998244353;
    const int_t g = 3;
    int_t power(int_t b, int_t i) {
        int_t r = 1;
        if (i < 0)
            i = (i % (mod - 1) + mod - 1) % (mod - 1);
        while (i) {
            if (i & 1)
                r = r * b % mod;
            b = b * b % mod;
            i >>= 1;
        }
        return r;
    }
    
    void makeflip(int_t* arr, int_t size2) {
        int_t len = (1 << size2);
        arr[0] = 0;
        for (int_t i = 1; i < len; i++) {
            arr[i] = (arr[i >> 1] >> 1) | ((i & 1) << (size2 - 1));
        }
    }
    
    int_t upper2n(int_t x) {
        int_t r = 0;
        while ((1 << r) < x)
            r++;
        return r;
    }
    template <int_t arg = 1>
    void transform(int_t* A, int_t size2, int_t* flip) {
        int_t len = (1 << size2);
        for (int_t i = 0; i < len; i++) {
            int_t r = flip[i];
            if (r > i)
                std::swap(A[i], A[r]);
        }
        for (int_t i = 2; i <= len; i *= 2) {
            int_t mr = power(g, arg * (mod - 1) / i);
            for (int_t j = 0; j < len; j += i) {
                int_t curr = 1;
                for (int_t k = 0; k < i / 2; k++) {
                    int_t u = A[j + k], v = curr * A[j + k + i / 2] % mod;
                    A[j + k] = (u + v) % mod;
                    A[j + k + i / 2] = (u - v + mod) % mod;
                    curr = curr * mr % mod;
                }
            }
        }
    }
    void poly_inv(const int_t* A, int_t n, int_t* result) {
        /*
        计算B(x)*A(x) = 1 mod x^n, 其中A(x)已知
        假设已知A(x)*B(x) = 1 mod x^{ceil(n/2)}
        假设C(x)*A(x) = 1 mod x^n
        (A(x)B(x)-1)^2 = A^2(x)B^2(x)-2A(x)B(x)+1= 0
        A(x)B^2(x)-2B(x)+C(x) = 0
        C(x) = 2B(x)-A(x)B^2(x)
        */
        if (n == 1) {
            result[0] = power(A[0], -1);
            return;
        }
        int_t next = n / 2 + n % 2;
        poly_inv(A, next, result);
        //次数不要选错了,应该用n次的A和B去卷
        int_t size2 = upper2n(n + 2 * next + 1);
        static int_t X[LARGE];
        static int_t Y[LARGE];
        int_t len = (1 << size2);
        memset(X + next, 0, sizeof(X[0]) * (len - n));
        memset(Y + next, 0, sizeof(Y[0]) * (len - next));
        memcpy(X, A, sizeof(A[0]) * n);
        memcpy(Y, result, sizeof(result[0]) * next);
        static int_t fliparr[LARGE];
        makeflip(fliparr, size2);
        transform<1>(X, size2, fliparr);
        transform<1>(Y, size2, fliparr);
    
        for (int_t i = 0; i < len; i++) {
            X[i] = (2 * Y[i] - X[i] * Y[i] % mod * Y[i] % mod + mod) % mod;
        }
        transform<-1>(X, size2, fliparr);
        const int_t inv = power(len, -1);
        for (int_t i = 0; i < n; i++)
            result[i] = X[i] * inv % mod;
    }
    int_t poly_divat(const int_t* F, const int_t* G, int_t n, int_t k) {
        /*
            n次多项式F和G
            计算F(x)/G(x)的k次项前系数
            考虑F(x)*G(-x)/G(x)*G(-x),分母只有偶数次项,写为C(x^2);分子写成xA(x^2)+B(x^2),如果k是奇数,那么递归(A,C,n,k/2),如果k是偶数,那么递归(B,C,n,k/2)
            到k<=n时直接计算
        */
        int_t size2 = upper2n(2 * n + 1);
        int_t len = 1 << size2;
        static int_t fliparr[LARGE];
        makeflip(fliparr, size2);
        static int_t T1[LARGE], T2[LARGE], T3[LARGE];
        for (int_t i = 0; i < len; i++) {
            if (i <= n)
                T1[i] = F[i], T2[i] = G[i];
            else
                T1[i] = T2[i] = T3[i] = 0;
        }
        const int_t inv = power(len, -1);
        while (k != 0) {
            for (int_t i = 0; i < len; i++) {
                if (i <= n) {
                    T3[i] = T2[i] * (i % 2 ? (mod - 1) : 1);
                } else
                    T3[i] = 0;
            }
            transform(T1, size2, fliparr);
            transform(T2, size2, fliparr);
            transform(T3, size2, fliparr);
            for (int_t i = 0; i < len; i++) {
                T1[i] = T1[i] * T3[i] % mod;
                T2[i] = T2[i] * T3[i] % mod;
            }
            transform<-1>(T1, size2, fliparr);
            transform<-1>(T2, size2, fliparr);
            for (int_t i = 0; i < len; i++) {
                if (i * 2 < len) {
                    T2[i] = T2[i * 2] * inv % mod;
                } else
                    T2[i] = 0;
            }
            int_t b = k % 2;
            for (int_t i = 0; i < len; i++) {
                if (i % 2 == b) {
                    T1[i / 2] = T1[i] * inv % mod;
                }
    
                if (i > 0)  //防止把T1[0]改为0
                    T1[i] = 0;
            }
            k >>= 1;
        }
    
        return T1[0] * power(T2[0], -1) % mod;
    }
    void poly_mul(const int_t* A, int_t n, const int_t* B, int_t m, int_t* C) {
        int_t size2 = upper2n(n + m + 1);
        int_t len = 1 << size2;
        static int_t T1[LARGE], T2[LARGE];
        for (int_t i = 0; i < len; i++) {
            if (i <= n)
                T1[i] = A[i];
            else
                T1[i] = 0;
            if (i <= m)
                T2[i] = B[i];
            else
                T2[i] = 0;
        }
        static int_t fliparr[LARGE];
        makeflip(fliparr, size2);
        transform(T1, size2, fliparr);
        transform(T2, size2, fliparr);
        for (int_t i = 0; i < len; i++)
            T1[i] = T1[i] * T2[i] % mod;
        transform<-1>(T1, size2, fliparr);
        int_t inv = power(len, -1);
        for (int_t i = 0; i <= n + m; i++)
            C[i] = T1[i] * inv % mod;
    }
    int main() {
        std::ios::sync_with_stdio(false);
        static int_t A[LARGE], F[LARGE];
        static int_t T1[LARGE], T2[LARGE];
        int_t n, k;
        cin >> n >> k;
        for (int_t i = 1; i <= k; i++) {
            cin >> F[i];
            F[i] = (F[i] % mod + mod) % mod;
        }
        F[0] = 0;
        for (int_t i = 0; i < k; i++) {
            cin >> A[i];
            A[i] = (A[i] % mod + mod) % mod;
        }
        A[k] = 0;
        poly_mul(A, k, F, k, T1);
        T1[0] = A[0];
        for (int_t i = 1; i <= k - 1; i++) {
            T1[i] = (A[i] - T1[i] + mod) % mod;
        }
        for (int_t i = k; i <= 2 * k; i++)
            T1[i] = 0;
        T2[0] = 1;
        for (int_t i = 1; i <= k; i++)
            T2[i] = (mod - F[i]) % mod;
        int_t r = poly_divat(T1, T2, k, n);
        cout << r << endl;
        return 0;
    }
    /*
    (1+2*x)/(1+x+x^2)
    2 5
    
    1 2 0
    1 1 1
    
    
    ans = -2 = 998244351
    
    */

    代码2: 递归到$k<n$时直接求逆运算,较快,大约是代码1的一半时间

    #include <cstring>
    #include <iostream>
    using int_t = long long int;
    using std::cin;
    using std::cout;
    using std::endl;
    
    const int_t LARGE = 2e6;
    
    const int_t mod = 998244353;
    const int_t g = 3;
    int_t power(int_t b, int_t i) {
        int_t r = 1;
        if (i < 0)
            i = (i % (mod - 1) + mod - 1) % (mod - 1);
        while (i) {
            if (i & 1)
                r = r * b % mod;
            b = b * b % mod;
            i >>= 1;
        }
        return r;
    }
    
    void makeflip(int_t* arr, int_t size2) {
        int_t len = (1 << size2);
        arr[0] = 0;
        for (int_t i = 1; i < len; i++) {
            arr[i] = (arr[i >> 1] >> 1) | ((i & 1) << (size2 - 1));
        }
    }
    
    int_t upper2n(int_t x) {
        int_t r = 0;
        while ((1 << r) < x)
            r++;
        return r;
    }
    template <int_t arg = 1>
    void transform(int_t* A, int_t size2, int_t* flip) {
        int_t len = (1 << size2);
        for (int_t i = 0; i < len; i++) {
            int_t r = flip[i];
            if (r > i)
                std::swap(A[i], A[r]);
        }
        for (int_t i = 2; i <= len; i *= 2) {
            int_t mr = power(g, arg * (mod - 1) / i);
            for (int_t j = 0; j < len; j += i) {
                int_t curr = 1;
                for (int_t k = 0; k < i / 2; k++) {
                    int_t u = A[j + k], v = curr * A[j + k + i / 2] % mod;
                    A[j + k] = (u + v) % mod;
                    A[j + k + i / 2] = (u - v + mod) % mod;
                    curr = curr * mr % mod;
                }
            }
        }
    }
    void poly_inv(const int_t* A, int_t n, int_t* result) {
        /*
        计算B(x)*A(x) = 1 mod x^n, 其中A(x)已知
        假设已知A(x)*B(x) = 1 mod x^{ceil(n/2)}
        假设C(x)*A(x) = 1 mod x^n
        (A(x)B(x)-1)^2 = A^2(x)B^2(x)-2A(x)B(x)+1= 0
        A(x)B^2(x)-2B(x)+C(x) = 0
        C(x) = 2B(x)-A(x)B^2(x)
        */
        if (n == 1) {
            result[0] = power(A[0], -1);
            return;
        }
        int_t next = n / 2 + n % 2;
        poly_inv(A, next, result);
        //次数不要选错了,应该用n次的A和B去卷
        int_t size2 = upper2n(n + 2 * next + 1);
        static int_t X[LARGE];
        static int_t Y[LARGE];
        int_t len = (1 << size2);
        memset(X + next, 0, sizeof(X[0]) * (len - n));
        memset(Y + next, 0, sizeof(Y[0]) * (len - next));
        memcpy(X, A, sizeof(A[0]) * n);
        memcpy(Y, result, sizeof(result[0]) * next);
        static int_t fliparr[LARGE];
        makeflip(fliparr, size2);
        transform<1>(X, size2, fliparr);
        transform<1>(Y, size2, fliparr);
    
        for (int_t i = 0; i < len; i++) {
            X[i] = (2 * Y[i] - X[i] * Y[i] % mod * Y[i] % mod + mod) % mod;
        }
        transform<-1>(X, size2, fliparr);
        const int_t inv = power(len, -1);
        for (int_t i = 0; i < n; i++)
            result[i] = X[i] * inv % mod;
    }
    int_t poly_divat(const int_t* F, const int_t* G, int_t n, int_t k) {
        /*
            n次多项式F和G
            计算F(x)/G(x)的k次项前系数
            考虑F(x)*G(-x)/G(x)*G(-x),分母只有偶数次项,写为C(x^2);分子写成xA(x^2)+B(x^2),如果k是奇数,那么递归(A,C,n,k/2),如果k是偶数,那么递归(B,C,n,k/2)
            到k<=n时直接计算
        */
        int_t size2 = upper2n(2 * n + 1);
        int_t len = 1 << size2;
        static int_t fliparr[LARGE];
        makeflip(fliparr, size2);
        static int_t T1[LARGE], T2[LARGE], T3[LARGE];
        for (int_t i = 0; i < len; i++) {
            if (i <= n)
                T1[i] = F[i], T2[i] = G[i];
            else
                T1[i] = T2[i] = T3[i] = 0;
        }
        const int_t inv = power(len, -1);
        while (k >= n) {
            for (int_t i = 0; i < len; i++) {
                if (i <= n) {
                    T3[i] = T2[i] * (i % 2 ? (mod - 1) : 1);
                } else
                    T3[i] = 0;
            }
            transform(T1, size2, fliparr);
            transform(T2, size2, fliparr);
            transform(T3, size2, fliparr);
            for (int_t i = 0; i < len; i++) {
                T1[i] = T1[i] * T3[i] % mod;
                T2[i] = T2[i] * T3[i] % mod;
            }
            transform<-1>(T1, size2, fliparr);
            transform<-1>(T2, size2, fliparr);
            for (int_t i = 0; i < len; i++) {
                if (i * 2 < len) {
                    T2[i] = T2[i * 2] * inv % mod;
                } else
                    T2[i] = 0;
            }
            int_t b = k % 2;
            for (int_t i = 0; i < len; i++) {
                if (i % 2 == b) {
                    T1[i / 2] = T1[i] * inv % mod;
                }
    
                if (i > 0)  //防止把T1[0]改为0
                    T1[i] = 0;
            }
            k >>= 1;
        }
    
        poly_inv(T2, k + 1, T3);
        int_t result = 0;
        //计算结果的k次项
        for (int_t i = 0; i <= k; i++) {
            result = (result + T1[i] * T3[k - i] % mod) % mod;
        }
        return result;
    }
    void poly_mul(const int_t* A, int_t n, const int_t* B, int_t m, int_t* C) {
        int_t size2 = upper2n(n + m + 1);
        int_t len = 1 << size2;
        static int_t T1[LARGE], T2[LARGE];
        for (int_t i = 0; i < len; i++) {
            if (i <= n)
                T1[i] = A[i];
            else
                T1[i] = 0;
            if (i <= m)
                T2[i] = B[i];
            else
                T2[i] = 0;
        }
        static int_t fliparr[LARGE];
        makeflip(fliparr, size2);
        transform(T1, size2, fliparr);
        transform(T2, size2, fliparr);
        for (int_t i = 0; i < len; i++)
            T1[i] = T1[i] * T2[i] % mod;
        transform<-1>(T1, size2, fliparr);
        int_t inv = power(len, -1);
        for (int_t i = 0; i <= n + m; i++)
            C[i] = T1[i] * inv % mod;
    }
    int main() {
        std::ios::sync_with_stdio(false);
        static int_t A[LARGE], F[LARGE];
        static int_t T1[LARGE], T2[LARGE];
        int_t n, k;
        cin >> n >> k;
        for (int_t i = 1; i <= k; i++) {
            cin >> F[i];
            F[i] = (F[i] % mod + mod) % mod;
        }
        F[0] = 0;
        for (int_t i = 0; i < k; i++) {
            cin >> A[i];
            A[i] = (A[i] % mod + mod) % mod;
        }
        A[k] = 0;
        poly_mul(A, k, F, k, T1);
        T1[0] = A[0];
        for (int_t i = 1; i <= k - 1; i++) {
            T1[i] = (A[i] - T1[i] + mod) % mod;
        }
        for (int_t i = k; i <= 2 * k; i++)
            T1[i] = 0;
        T2[0] = 1;
        for (int_t i = 1; i <= k; i++)
            T2[i] = (mod - F[i]) % mod;
        int_t r = poly_divat(T1, T2, k, n);
        cout << r << endl;
        return 0;
    }
    /*
    (1+2*x)/(1+x+x^2)
    2 5
    
    1 2 0
    1 1 1
    
    
    ans = -2 = 998244351
    
    */

     

  • Nginx路由匹配模拟器

    https://nginx.viraptor.info/

    真是我的救星,调路由调吐了

  • CFGYM102394L CCPC2019哈尔滨 L题 LRU Algorithm

    首先做法很简单:令缓存大小为n,然后直接把操作模拟一遍,后期如果我们限制了缓存大小为x,那就等价于取我们在模拟时长度为x的前缀。

    然后我们显然可以把前缀哈希算一算,插到`std::unordered_map`里,然后成功TLE。

    然后我们考虑另一个做法:把询问插到字典树里,仍然对操作进行模拟,每模拟一次后,在字典树上把序列走一遍,并标记对应的询问为Yes。

    但是有几个地方要注意

    • 一个点可能对应多个询问,所以用一个vector来挂询问吧。
    • 一组询问在去除后缀0之后可能长度为0,此时询问是被挂在根上的,务必进行处理,否则WA!
    #include <assert.h>
    #include <algorithm>
    #include <cstring>
    #include <iostream>
    #include <unordered_map>
    #include <vector>
    using int_t = int;
    
    using std::cin;
    using std::cout;
    using std::endl;
    
    using map_t = std::unordered_map<int_t, struct Node*>;
    const int_t LARGE = 5e3 + 20;
    char inputbuf[(int64_t)1e8];
    char* head = inputbuf;
    void initinput() {
        fread(inputbuf, 1, sizeof(inputbuf), stdin);
    }
    char nextchar() {
        assert(head <= inputbuf + sizeof(inputbuf));
        return *(head++);
    }
    template <class T>
    void read(T& x) {
        x = 0;
        char chr = nextchar();
        while (chr < '0' || chr > '9')
            chr = nextchar();
        while (chr >= '0' && chr <= '9') {
            x = x * 10 + chr - '0';
            chr = nextchar();
        }
    }
    
    struct Node {
        std::vector<bool*> result;
        map_t chd;
        ~Node() {
            for (const auto& kvp : chd)
                delete kvp.second;
        }
    };
    
    int_t arr[LARGE + 1];
    int_t arr1[LARGE + 10], queue[LARGE + 1];
    bool result[LARGE + 1];
    int main() {
        initinput();
        int_t T;
        read(T);
        while (T--) {
            queue[0] = 0;
            int_t n, q;
            read(n), read(q);
            Node* root = new Node;
            for (int_t i = 1; i <= n; i++) {
                read(arr[i]);
            }
            for (int_t i = 1; i <= q; i++) {
                result[i] = false;
                Node* curr = root;
                int_t len;
                read(len);
    #ifdef DEBUG
                cout << "insert len " << len << endl;
    #endif
                for (int_t j = 1; j <= len; j++) {
                    int_t x;
                    read(x);
                    if (x == 0)
                        continue;
    #ifdef DEBUG
                    cout << "insert " << x << endl;
    #endif
                    auto& ref = curr->chd[x];
                    if (ref == nullptr)
                        ref = new Node;
                    curr = ref;
                }
                curr->result.push_back(&result[i]);
    #ifdef DEBUG
                cout << "insert ok, result to " << i << endl;
    #endif
            }
            for (int_t i = 1; i <= n; i++) {
                int_t x = arr[i];
                arr1[0] = 0;
                arr1[++arr1[0]] = x;
                for (int_t j = 1; j <= queue[0]; j++) {
                    if (queue[j] != x)
                        arr1[++arr1[0]] = queue[j];
                }
                // arr1[0] = std::min(arr1[0], n);
                assert(arr1[0] <= n);
                memcpy(queue, arr1, sizeof(arr1[0]) * (n + 1));
                Node* curr = root;
    #ifdef DEBUG
                cout << "mapping seq ";
                for (int_t i = 1; i <= queue[0]; i++)
                    cout << queue[i] << " ";
                cout << endl;
    #endif
                for (int_t i = 1; i <= queue[0]; i++) {
                    if (!curr->chd.count(queue[i]))
                        break;
                    else
                        curr = curr->chd[queue[i]];
    #ifdef DEBUG
                    cout << "walk with " << queue[i] << endl;
    #endif
                    for (auto ptr : curr->result) {
                        *ptr = true;
    #ifdef DEBUG
                        cout << "mark result " << (ptr - result) << " to true"
                             << endl;
    #endif
                    }
                }
            }
            for (auto x : root->result)
                *x = true;
            for (int_t i = 1; i <= q; i++) {
                if (result[i]) {
                    puts("Yes");
                } else {
                    puts("No");
                }
            }
            delete root;
        }
        return 0;
    }

     

  • CFGYM102394E CCPC2019哈尔滨 E题 Exchanging Gifts

    假设我们能维护出最终序列的长度L和最终序列出现最多的数的个数cnt,假设$cnt\leq\frac L 2$ ,那么答案是L,这时候我们把序列升序和降序对起来就构造出n的结果。
    假设$cnt>\frac L 2$,那么答案是$2(L-cnt)$,我们仍然把序列升序和降序对应起来,答案是$L-(cnt-(L-cnt))=2*(L-cnt)$
    比如
    “`
    1 2 3 3 3 3 3
    3 3 3 3 3 2 1
    “`
    中间有3个3是重了的,那么重的部分有cnt-(L-cnt)个,其中L-cnt表示的是`1 2`的长度,所以总答案是L-(cnt-(L-cnt))
    现在的问题在于如何维护出这个众数,并且判定$cnt\leq \frac L 2$是否成立。

     

    考虑一种线性时间求求序列众数(出现次数大于一半)的方法:
    令f(i)表示我们从头开始扫到第i个元素的时候(用$a_i$表示),序列中出现次数最多的数与其他的数出现次数之和的差值。同时我们需要维护这个数是多少(用x来表示)。
    每次扫到$a_i$的时候:
    – 如果$a_i=x$,那么f(i)=f(i-1)+1,x不变
    – 如果$a_i\neq x$,那么f(i)=f(i-1)-1,然后如果$f(i)<0$,那么x变为$a_i$,同时$f(i)$取反。

     

    对于这个题,如果我们要使用这种求众数的方法,核心在于如何考虑操作2(合并两个序列时)如何处理。
    我们维护每个序列的f和x,合并两个序列的时候:
    – 如果他们的f相同,那么新序列的f不变,x相加。
    – 如果他们的f不同,那么考虑下两个序列的哪一个的x比较大。如果他们相同,那么新序列的f就写为0(这时候可能存在两个众数),x从原来的x里随便选一个。如果他们不同,那x选择为f较大的那一个,同时f设置为较大值减掉较小值。

     

    所以对于这个题,我们先用这种求众数的方法算出来最终序列的众数。
    但此时求出来的众数,仅仅是在保证`存在一个出现次数超过序列长度一半时候`的众数,如果不存在这样子的众数,也会得出来一个结果,但是并不具有意义。
    所以我们再跑一遍递推,求出来我们上一步求的众数的出现次数,然后我们就可以照着前文所述来算答案了。
    *此外,这个题卡常*
    我跑了半天性能分析后大概知道了几个卡常的点:
    1. 输入量非常巨大,请考虑一次性读入输入数据后自行用缓冲区处理输入。
    2. `std::vector`的构造函数非常慢(性能分析显示,$10^6$次调用大约花了80ms),所以不要使用vector来存储变长的序列,考虑自行分配-回收内存。
    3. `State`结构体的构造函数占了大概40ms的时间,考虑强制内联。
    另外,读入函数千万不要写错了!不要写成`chr>=’9’`!
    #pragma GCC optimize("O2")
    #include <assert.h>
    #include <inttypes.h>
    #include <algorithm>
    #include <iostream>
    #include <vector>
    
    
    using int_t = int;
    using std::cin;
    using std::cout;
    using std::endl;
    const int_t LARGE = 1e6;
    char inputbuf[(int(2e8) / 1024) * 1024];
    char* head = inputbuf;
    inline char nextchar() {
        return *(head++);
    }
    void initinput() {
        fread(inputbuf, 1024, sizeof(inputbuf) / 1024, stdin);
    }
    struct State {
        int64_t len;
        int_t mostval;
        int64_t mostcount;
        inline State(int64_t len = 0, int_t mostval = 0, int64_t mostcount = 0)
            : len(len), mostval(mostval), mostcount(mostcount) {}
        State operator+(const State& rhs) const {
            State result(len + rhs.len, 0, 0);
            if (mostval == rhs.mostval)
                result.mostval = mostval,
                result.mostcount = mostcount + rhs.mostcount;
            else {
                if (mostcount > rhs.mostcount) {
                    result.mostcount = mostcount - rhs.mostcount;
                    result.mostval = mostval;
                } else {
                    result.mostcount = rhs.mostcount - mostcount;
                    result.mostval = rhs.mostval;
                }
            }
            return result;
        }
    };
    std::ostream& operator<<(std::ostream& os, const State& state) {
        os << "State{len=" << state.len << ",mostval=" << state.mostval
           << ",mostcount=" << state.mostcount << "}";
        return os;
    }
    struct Opt {
        int_t type;
        int_t* data;
        int_t datalen;
        int_t x1, x2;
        int64_t mostcount = 0;
    } opts[LARGE + 1];
    State dp[LARGE + 1];
    int_t n;
    template <class T>
    void read(T& x) {
        x = 0;
        char chr = nextchar();
        while (chr < '0' || chr > '9')
            chr = nextchar();
        while (chr >= '0' && chr <= '9') {
            x = x * 10 + chr - '0';
            chr = nextchar();
        }
        assert(x >= 0);
    }
    template <class T>
    void write(T x) {
        assert(x >= 0);
        if (x > 9)
            write(x / 10);
        putchar('0' + x % 10);
    }
    int main() {
        // freopen("input.txt", "r", stdin);
        initinput();
        int_t T;
        read(T);
        while (T--) {
            read(n);
            for (int_t i = 1; i <= n; i++) {
                auto& ref = opts[i];
                if (ref.data) {
                    delete[] ref.data;
                    ref.data = nullptr;
                }
                dp[i] = State();
                read(ref.type);
                if (ref.type == 1) {
                    int_t k;
                    read(k);
                    int_t sum = 0, val = 0;
                    ref.data = new int_t[k + 1];
                    ref.datalen = k;
                    for (int_t i = 1; i <= k; i++) {
                        int_t x;
                        read(x);
                        ref.data[i] = x;
                        if (x == val)
                            sum++;
                        else
                            sum--;
                        if (sum < 0) {
                            sum *= -1, val = x;
                        }
                    }
                    // ref.seq.shrink_to_fit();
                    dp[i] = State(k, val, sum);
                } else {
                    read(ref.x1);
                    read(ref.x2);
                }
            }
            for (int_t i = 1; i <= n; i++) {
                const auto& ref1 = opts[i];
                if (ref1.type == 2) {
                    dp[i] = dp[ref1.x1] + dp[ref1.x2];
                }
            }
    #ifdef DEBUG
            for (int_t i = 1; i <= n; i++) {
                cout << "dp " << i << " = " << dp[i] << endl;
            }
    #endif
            int_t mostval = dp[n].mostval;
            for (int_t i = 1; i <= n; i++) {
                auto& ref = opts[i];
                if (ref.type == 1)
                    ref.mostcount = std::count(ref.data + 1,
                                               ref.data + 1 + ref.datalen, mostval);
                else
                    ref.mostcount = opts[ref.x1].mostcount + opts[ref.x2].mostcount;
            }
            int64_t mostcount = opts[n].mostcount;
    #ifdef DEBUG
            cout << "final len " << dp[n].len << endl;
            cout << "mostcount " << mostcount << endl;
            cout << "mostval " << mostval << endl;
    #endif
            if (mostcount * 2 <= dp[n].len) {
                write(dp[n].len);
            } else {
                write(2 * (dp[n].len - mostcount));
            }
            puts("");
        }
        return 0;
    }

     

  • CFGYM102394I CCPC2019哈尔滨 I题 Interesting Permutations

    签到题我都不会做,我爬了。

    首先检测一些明显非法的情况:

    1. $h_i\geq n$,明显不可能,$h_i$上限是$n-1$

    2. $h_i<h_{i-1}$(对于$i\geq 2$),最大值不会减小,最小值不会增大,所以$h_i$一定是单调不减的。

    3. $h_1\neq 0$,这很显然。

    4. $h_n\neq n-1$这也很显然。

    然后我们考虑从第$2$个元素开始枚举,我们维护一个`gap`变量,用以存储”在当前这个位置,一共有多少个位置可以填数,并且保证填了数之后不影响当前位置前缀最大值和前缀最小值的取值”。当前枚举到第$i$个元素时:

    • 如果$h_i=h_{i-1}$,那就说明当前这个位置的值没有改变前缀最值的分布,那么总答案就可以乘上`gap`(因为有这么多的方案数让我们来填,并且填了后不影响前缀最值),并且 `gap`要减掉1,因为我们填了个数后占了一个空位。
    • 如果$h_i>h_{i-1}$,那就说明当前这个位置的值改变了前缀最大值或者前缀最小值二者之一,那么总答案乘上2。同时`gap`要加上$h_i-h_{i-1}-1$,因为我们新创造了这么多的空位。
    #include <algorithm>
    #include <cstdio>
    #include <iostream>
    
    using int_t = long long;
    
    using std::cin;
    using std::cout;
    using std::endl;
    
    const int_t mod = 1e9 + 7;
    const int_t LARGE = 1e5 + 10;
    int_t arr[LARGE + 1];
    int_t n;
    int main() {
        std::ios::sync_with_stdio(false);
        int_t T;
        cin >> T;
        while (T--) {
            cin >> n;
            bool fail = false;
            for (int_t i = 1; i <= n; i++) {
                cin >> arr[i];
                if (arr[i] < arr[i - 1])
                    fail = true;
                if (i == 1 && arr[i] != 0)
                    fail = true;
                if (i == n && arr[i] != n - 1)
                    fail = true;
                if (arr[i] >= n)
                    fail = true;
            }
            if (fail) {
                cout << 0 << endl;
                continue;
            }
            int_t prod = 1;
            int_t sec = 0;
            for (int_t i = 2; i <= n; i++) {
                if (arr[i] == arr[i - 1]) {
                    prod = prod * sec % mod;
                    sec--;  //消耗了一个中间空位
                }
                if (arr[i] > arr[i - 1]) {
                    prod = prod * 2 % mod;
                    sec = (sec + arr[i] - arr[i - 1] - 1 + mod) % mod;
                }
            }
            cout << prod << endl;
        }
        return 0;
    }

     

     

  • HelloJudge2开发笔记

    总得写点东西吧。

    实际上这篇是为了防止半年后我再看自己代码看不懂的情况发生。

     

    整个项目分为Web端和评测机端,分别是gitee上的两个项目。

    https://gitee.com/yutong_java/HelloJudge2

    https://gitee.com/yutong_java/HelloJudge2-Judger

    主体开发语言为Python3.7+ES7+HTML5+CSS3+Cpp11。

    Web后端采取Flask作为框架,前端采取Vue+jQuery作为框架(其中Vue负责页面渲染,jQuery负责AJAX),整体开发采取前后端分离。

    后端路由/api/xxxx为前端调用的API接口,/ws/xxx为后端与前端实时通信的WebSocket的namespace(主要用来实时推送评测状态,私信,通知)。

    后端所有模板页面继承自base.html,模板变量仅限APP_NAME(应用名)和DEBUG(调试模式)两个,其他所有数据都应该在前端渲染。

    后端收到新的提交后,会存到数据库里,然后调用Celery增加一个新的Task,评测端收到新的Task后开始同步评测。

    评测端使用Docker作为评测容器,每一次评测创建一个新的容器,评测完毕之后销毁,根文件系统挂载为只读。

    在评测的的docker容器启动后一直阻塞,直到容器进程不存在,阻塞期间死循环读取容器的内存占用,此部分用C++实现。

     

  • 声明

    我已经退役了,我仍然会维持这个博客的运转,不过应该不会再有新的内容了。

    也可能之后会有一些关于实际开发中遇到的问题吧。

  • BJOI2019 删数

    考虑$p\neq 0$的情况。

    使用$a_i$表示数字i出现的次数。

    构建一棵线段树,令$a_i$覆盖$[i-a_i+1,a_i]$的区间,最终$[1,n]$内为0的位置的个数即为答案。

    很显然一个位置为0,我们必定要调整之后的某个数到这个位置来覆盖它。

    带整体加减?

    区间平移。

    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    #include <vector>
    #include <inttypes.h>
    #define debug(x) std::cout << #x << " = " << x << std::endl;
    
    typedef long long int int_t;
    using std::cin;
    using std::endl;
    using std::cout;
    
    const int_t LARGE = 5e5;
    
    struct State {
    	int_t val,count;
    	State operator+(const State& rhs) const {
    		State next=*this;
    		if(rhs.val==next.val) next.count+=rhs.count;
    		else if(rhs.val<next.val) {
    			next=rhs;
    		}
    		return next;
    	}
    };
    struct Node {
    	Node*left,*right;
    	State state {0,1};
    	int_t mark=0;
    	int_t begin,end;
    	Node(int_t begin,int_t end) {
    		this->begin=begin,this->end=end;
    	}
    	void add(int_t x) {
    //        minval+=x;
    		state.val+=x;
    		mark+=x;
    	}
    	void pushDown() {
    		if(mark) {
    			left->add(mark),right->add(mark);
    			mark=0;
    		}
    	}
    	void maintain() {
    		if(begin==end) return;
    		state=left->state+right->state;
    	}
    	static Node* build(int_t begin,int_t end) {
    		Node* node=new Node(begin,end);
    		if(begin!=end) {
    			int_t mid=(begin+end)/2;
    			node->left=build(begin,mid);
    			node->right=build(mid+1,end);
    		}
    		node->maintain();
    		return node;
    	}
    	void add(int_t begin,int_t end,int_t x) {
    		if(end<this->begin||begin>this->end) return;
    		if(this->begin>=begin&&this->end<=end) {
    			this->add(x);
    			return;
    		}
    		pushDown();
    		left->add(begin,end,x);
    		right->add(begin,end,x);
    		maintain();
    	}
    	State query(int_t begin,int_t end) {
    		if(this->begin>=begin&&this->end<=end) return state;
    		int_t mid=(this->begin+this->end)/2;
    		pushDown();
    		if(end<=mid) return left->query(begin,end);
    		else if(begin>mid) return right->query(begin,end);
    		return left->query(begin,mid)+right->query(mid+1,end);
    	}
    };
    int_t count[LARGE+1];
    int_t seq[LARGE+1];
    int_t n,m;
    Node* root;
    int_t lOff,rOff;
    //ÈÃij¸öÊý½øÈë/Í˳ö
    void modify(int_t x,int_t opt) {
    	if(opt==1) count[x]+=opt;
    #ifdef DEBUG
    	cout<<"exec pos "<<x-count[x]+1<<" "<<opt<<" number "<<x<<endl;
    	cout<<"before exec count = "<<count[x]<<endl;
    #endif
    	root->add(x-count[x]+1,x-count[x]+1,opt);
    	if(opt==-1) count[x]+=opt;
    }
    int main() {
    	scanf("%lld%lld",&n,&m);
    
    	lOff=std::max(n,m)+1;
    	rOff=lOff+n-1;
    	for(int i=1; i<=n; i++) {
    		int_t x;
    		scanf("%lld",&x);
    		x+=std::max(n,m);
    		count[x]++;
    		seq[i]=x;
    	}
    	root=Node::build(1,3*std::max(n,m));
    	for(int_t i=lOff; i<=rOff+1; i++) {
    #ifdef DEBUG
    		cout<<"count "<<i<<" = "<<count[i]<<" cover "<<i-count[i]+1<<" to "<<i<<endl;
    #endif
    		if(count[i])
    			root->add(i-count[i]+1,i,1);
    	}
    	for(int_t i=1; i<=m; i++) {
    		int_t opt,x;
    		scanf("%lld%lld",&opt,&x);
    		if(opt==0) {
    			if(x==1) {
    				if(count[rOff]) {
    					root->add(rOff-count[rOff]+1,rOff,-1);
    				}
    				lOff--,rOff--;
    			} else {
    				lOff++,rOff++;
    				if(count[rOff]) {
    					root->add(rOff-count[rOff]+1,rOff,1);
    				}
    
    			}
    #ifdef DEBUG
    			cout<<"moved to "<<lOff<<" "<<rOff<<endl;
    #endif
    
    		} else {
    			if(seq[opt]<=rOff)
    				root->add(seq[opt]-count[seq[opt]]+1,seq[opt]-count[seq[opt]]+1,-1);
    			count[seq[opt]]-=1;
    			seq[opt]=x+lOff-1;
    			modify(seq[opt],1);
    
    		}
    		auto ret=root->query(lOff,rOff);
    		if(ret.val!=0) {
    			printf("0\n");
    		} else {
    			printf("%lld\n",ret.count);
    		}
    #ifdef DEBUG
    		for(int_t i=1; i<=rOff; i++) cout<<i<<" = "<<root->query(i,i).val<<endl;
    #endif
    
    	}
    	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;
    }

     

  • CQOI2011 动态逆序对

    首先考虑如何转成一个偏序问题。

    把样例的过程画出图来。

    可以注意到一个时间点t产生的逆序对,在比他靠前的时间里仍然存在。

    所以我们可以考虑单独计算每个时间点因为删掉元素而丢掉的逆序对数。

    考虑搞出来n个三元组,每个形如(time,pos,val),表示第time个时间点把位于pos的元素val删掉。

    如果有元素没有被删除那么他们的time顺次往下排即可。

    一个三元组a会对另一个时间的三元组b产生1的贡献,当且仅当a.time>b.time(a比b晚删除,根据上面的图感性理解一下),然后(a.pos<b.pos且a.val>b.val)或者(a.pos>b.pos且a.val<b.val)。

    因为我们统计的是每个时间点因为删除而消失的逆序对个数,求一个后缀和,然后输出前m个就是答案。

    注意删除的是元素而不是下标!!

    #include <assert.h>
    #include <algorithm>
    #include <cstdio>
    #include <iostream>
    #include <vector>
    using int_t = long long int;
    using std::cin;
    using std::cout;
    using std::endl;
    
    const int_t LARGE = 5e5;
    
    struct Triple {
        int_t time, pos, val;
        int_t* target;
    } datas[LARGE + 1];
    int_t used = 0;
    int_t results[LARGE + 1];
    int_t perm[LARGE + 1];
    int_t n, m;
    int_t arr[LARGE + 1];
    
    std::ostream& operator<<(std::ostream& os, const Triple& a) {
        os << "Triple{time=" << a.time << ",pos=" << a.pos << ",val=" << a.val
           << ",target=" << (a.target - results) << ",targetval=" << *a.target
           << "}";
        return os;
    }
    int_t lowbit(int_t x) { return x & -x; }
    int_t query(int_t x) {
    #ifdef DEBUG
        cout << "query " << x << " = ";
    #endif
        int_t result = 0;
        while (x >= 1) {
            result += arr[x];
            assert(arr[x] >= 0);
            x -= lowbit(x);
        }
        return result;
    }
    void add(int_t pos, int_t val) {
        while (pos <= n) {
            arr[pos] += val;
            pos += lowbit(pos);
        }
    }
    void reset(int_t pos) {
        while (pos <= n) {
            arr[pos] = 0;
            pos += lowbit(pos);
        }
    }
    void process(Triple* left, Triple* right) {
        if (left >= right) return;
        auto mid = left + (right - left) / 2;
        process(left, mid);
        process(mid + 1, right);
    #ifdef DEBUG
    
        cout << "processing left\n";
        for (auto ptr = left; ptr <= mid; ptr++) cout << *ptr << endl;
        cout << "processing right\n";
        for (auto ptr = mid + 1; ptr <= right; ptr++) cout << *ptr << endl;
    
    #endif
        auto lptr = left, rptr = mid + 1;
        std::vector<Triple> result;
    #ifdef DEBUG
    
        cout << "before merge ";
        for (int_t i = 1; i <= n; i++) cout << arr[i] << " ";
        cout << endl;
    #endif
        //统计a.pos<b.pos且a.val>b.val的个数
        while (lptr <= mid && rptr <= right) {
    #ifdef DEBUG
            cout << "lptr pos = " << lptr->pos << " rptr pos = " << rptr->pos
                 << endl;
    #endif
            if (lptr->pos <= rptr->pos) {
                add(lptr->val, 1);
                result.push_back(*lptr);
    #ifdef DEBUG
                cout << "put " << *lptr << " to left" << endl;
    #endif
                lptr++;
    
            } else {
                *rptr->target += query(n) - query(rptr->val);
    #ifdef DEBUG
                cout << "put " << *rptr << " to right "
                     << "with val " << query(n) << " - " << query(rptr->val)
                     << endl;
    #endif
                result.push_back(*rptr);
                rptr++;
            }
        }
        while (rptr <= right) {
            *rptr->target += query(n) - query(rptr->val);
    #ifdef DEBUG
            cout << "put " << *rptr << " to right "
                 << "with val " << query(n) << " - " << query(rptr->val) << endl;
            cout << "n = " << n << ",rptr val = " << rptr->val << endl;
    #endif
            result.push_back(*rptr);
            rptr++;
        }
        while (lptr <= mid) result.push_back(*(lptr++));
        assert(result.size() == right - left + 1);
        for (auto p = left; p <= right; p++) reset(p->val);
    #ifdef DEBUG
        cout << "sort ok0 " << endl;
        for (const auto& x : result) cout << x << endl;
    
    #endif
        //统计a.pos>b.pos且a.val<b.val的个数
        lptr = mid, rptr = right;
        //倒着归并
        while (lptr >= left && rptr >= mid + 1) {
            if (lptr->pos > rptr->pos) {
                add(lptr->val, 1);
                lptr--;
            } else {
                *rptr->target += query(rptr->val - 1);
                rptr--;
            }
        }
        while (rptr >= mid + 1) {
            *rptr->target += query(rptr->val - 1);
            rptr--;
        }
        for (auto p = left; p <= right; p++) reset(p->val);
    
        std::copy(result.begin(), result.end(), left);
    #ifdef DEBUG
        cout << "sort ok1 " << endl;
        for (auto p = left; p <= right; p++) cout << *p << endl;
        cout << endl;
    #endif
    }
    int main() {
        scanf("%lld%lld", &n, &m);
        for (int_t i = 1; i <= n; i++) {
            scanf("%lld", &perm[i]);
        }
        static int_t inv[LARGE + 1];
        for (int_t i = 1; i <= n; i++) inv[perm[i]] = i;
        for (int_t i = 1; i <= m; i++) {
            //删的是元素,而不是下标!
            int_t val;
            scanf("%lld", &val);
            used++;
            datas[used] = Triple{used, inv[val], val, &results[i]};
            perm[inv[val]] = 0;
        }
        for (int_t i = 1; i <= n; i++) {
            if (perm[i]) {
                used++;
                datas[used] = Triple{used, i, perm[i], &results[used]};
            }
        }
        std::sort(datas + 1, datas + 1 + n,
                  [](const Triple& a, const Triple& b) { return a.time > b.time; });
        process(datas + 1, datas + n);
        // for (int_t i = 1; i <= n; i++) cout << results[i] << endl;
        for (int_t i = n; i >= 2; i--) {
            assert(results[i] >= 0);
            results[i - 1] += results[i];
        }
        for (int_t i = 1; i <= m; i++) {
            printf("%lld\n", results[i]);
        }
        return 0;
    }