• USACO 2020 January Contest, Platinum Problem 2. Non-Decreasing Subsequences


    题目链接:http://usaco.org/index.php?page=viewproblem2&cpid=997

    题解:矩阵在DP中的应用。

      对于区间1-i,我们假设我们的合法子序列方案数为∑f[i][0][j], f[i][j][k] 就表示在这个区间里以j开头以k结尾的方案数。也就说我们有N个K*K的矩阵。但很明显这样是不满足区间减法的。我们可以发现,每加入一个元素x,我们有一个贡献矩阵把矩阵f[i-1]转移到f[i],假设这个贡献矩阵是A[i],那么f[i]=A[i]*A[i-1]......*A[1],假如我想知道区间的贡献矩阵之积,我们可以用“逆矩阵”。我们设invf[i]=A'[1]*A'[2].....*A'[i],那么区间的贡献矩阵是f[R]*invf[L-1],这样我们可以做到O((Q+N)K^3),这只能过一部分数据。研究一下这些贡献矩阵发现,它是一个三角矩阵,并且只有一行(或者一列,取决于你的贡献矩阵是左乘还是右乘)和单位矩阵不一样,也就说我们可以在预处理f[i]和invf[i]的时候把矩阵乘法加速到K^2,然后答案的区间矩阵我们只需要知道一列,然后我们可以通过预处理加速到K。总的时间复杂度是O(NK^2+QK)。

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int modu = 1e9 + 7;
    typedef long long ll;
    const ll inverse2 = (1e9+8)/2;
    ll f[22][22], invf[22][22];
    ll pre[50005][22], ipre[50003][22];
    int N, K, Q, cur;
    int A[50003];
    
    int main() {
        freopen("nondec.in", "r", stdin);
        freopen("nondec.out", "w", stdout);
        scanf("%d%d", &N, &K);
        memset(f, 0, sizeof(f));
        memset(invf, 0, sizeof(invf));
        for (int i = 0; i <= K; ++i) f[i][i] = invf[i][i] = 1;
        ipre[0][0] = 1;
        for (int i = 1; i <= N; ++i) {
            scanf("%d", &A[i]);
          //手动模拟矩阵乘法,因为有系数2,所以每个贡献矩阵左乘拆成两种基本行变换,把A[i]行*2,和把前面的行加到这一行。
    for (int j = 0; j < A[i]; ++j) { ll sumf = 0; for (int k = 0; k < A[i]; ++k) { sumf = (sumf + f[k][j]*inverse2) % modu; } f[A[i]][j] = (f[A[i]][j] + sumf) % modu; } for (int j = 0; j <= K; ++j) f[A[i]][j] = f[A[i]][j]*2 % modu; for (int j = 0; j <= K; ++j) for (int k = 0; k <= K; ++k) pre[i][j] = (pre[i][j] + f[k][j]) % modu;
          //右乘可以看成基本列变换
    for (int j = 0; j <= K; ++j) invf[j][A[i]] = invf[j][A[i]]*inverse2 % modu; for (int j = A[i]; j <= K; ++j) { for (int k = 0; k < A[i]; ++k) invf[j][k] = (invf[j][k] - invf[j][A[i]]) % modu; } for (int j = 0; j <= K; ++j) ipre[i][j] = invf[j][0]; } scanf("%d", &Q); while (Q--) { int L, R; scanf("%d%d", &L, &R); ll ans = 0; for (int i = 0; i <= K; ++i) ans = (ans + pre[R][i]*ipre[L-1][i]) % modu; printf("%lld ", (ans + modu) % modu); } return 0; }
  • 相关阅读:
    [ACM]线段树
    [ACM]树形结构基础 & 字典树
    [ACM]前缀和 & 差分 & 位运算 & Hash函数
    [ACM] 贪心 & 栈 & 队列 & 优先队列
    [ACM] BFS & 双端BFS & A* & 双边BFS
    [ACM]Two Point & 尺取 & 离散化 & C++STL( struct重写,容器应用 )
    JavaWeb期末
    [数据结构]权值线段树与可持久化线段树(主席树)
    [数字图像处理](六)插值运算
    [数字图像处理](五)AHE
  • 原文地址:https://www.cnblogs.com/albert7xie/p/12507414.html
Copyright © 2020-2023  润新知