CCPC 2020 秦皇岛站 H题

放个codeforces链接 点我

主要参照YaoBIG的题解,这里记录一下题解中没提到的细节

首先是套路的拆贡献,对于每一个$t$,答案为$\left \{ a_i = k \right \} + 2\left \{ a_i = a_j = k (j < i) \right \}$

然后转化为枚举$i$和$(i, j)$统计每一个序列的贡献

先看$\left \{ a_i = k \right \}$中是怎么做的,对于一个位置$i$如果填上$k$,很自然地可以想到分为$k$是首次出现和$k$不是首次出现两种情况。

1、$k$如果是首次出现,那么$1- (i - 1)$的位置一定出现过了$1, 2, \cdots, k - 1$这些数,$i$后面的数可以填$1, 2, 3, 4, \cdots, k$以及$k + 1$,可以首先设$f(i, k)$表示长度为$i$的,已经最大值为$k$的方案数,然后考虑$i$后面的数怎么填 ,可以设$g(i, k)$表示从最大值为$k$的情况开始填,一直填$i$位的方案数,那么第一种情况的答案就是$f(i - 1, k - 1) * g(n - i, k)$。

2、考虑$k$不是第一次出现,(我想不出来)那么$1 - (i  - 1)$的位置一定已经出现过了一个$j$满足$a_j$第一个$= k$,发现这样子的情况和1、中几乎完全一致,为 $f(j - 1, k - 1) * g(n - j - 1, k)$

那么$\left \{ a_i = t \right \}$的答案就是$f(i - 1, k - 1) * g(n - i, k) + \sum_{j = 1}^{i - 1}f(j - 1, k - 1) * g(n - j - 1, k)$,外层枚举$k, i$,做个前缀和

然后考虑$\left \{ a_i = a_j = k (j < i) \right \}$怎么做,发现和$j$位置上的$k$是不是第一次出现有关,一个$(j, i)$的答案为$f(j - 1, k - 1) * g(n - j - 1, k) + \sum_{t = 1}^{j - 1}f(t - 1, k - 1) * g(n - t - 2, k)$,$i$对这个答案没啥影响,一个$j$的答案即为一个$(j, i)$的答案直接乘上$(n - j)$

 

现在考虑$f$和$g$怎么做,$f$的初值应该为$f(0, 0) = f(1, 1) = 1$,这里注意只有$f(0, 0)$代表啥都没有,是唯一一个$f(0, ?)$中的合法状态,如果有了$f(i, j)$,现在填第$i + 1$位,一种可行的填法是填$j + 1$,所以转移到$f(i + 1, j + 1)$,另外一种可行的填法是填$1 - j$中的任意一个数,所以也可以转移到$f(i + 1, j)$;而$g$的初值是$g(0, ?) = 1$,当我们从$g(i, ?)$转移到$g(i + 1, j)$时,我们考虑把之后填的数放到序列的后面,填上一个数当作这个序列的第一位,那么如果第一次填了$j$,是从$g(i, j - 1)$转移来的,如果不是第一次填$j$,则是从$g(i, j)$转移来的。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
typedef pair <int, int> pin;

const int N = 3005;

int testCase, n;
ll P, ans1[N], ans2[N], ans[N], f[N][N], g[N][N], sum1[N], sum2[N];

namespace Fread {
    const int L = 1 << 15;
    
    char buffer[L], *S, *T;
    
    inline char Getchar() {
        if(S == T) {
            T = (S = buffer) + fread(buffer, 1, L, stdin);
            if(S == T) return EOF;
        }
        return *S++;
    }
    
    template <class T> 
    inline void read(T &X) {
        char ch; T op = 1;
        for(ch = Getchar(); ch > '9' || ch < '0'; ch = Getchar())
            if(ch == '-') op = -1;
        for(X = 0; ch >= '0' && ch <= '9'; ch = Getchar()) 
            X = (X << 1) + (X << 3) + ch - '0'; 
        X *= op;
    }
    
} using namespace Fread;   

namespace Fwrite {
    const int L = 1 << 15;
    
    char buf[L], *pp = buf;
    
    void Putchar(const char c) {
        if(pp - buf == L) fwrite(buf, 1, L, stdout), pp = buf;
        *pp++ = c;
    }
    
    template<typename T>
    void print(T x) {
        if(x < 0) {
            Putchar('-');
            x = -x;
        }
        if(x > 9) print(x / 10);
        Putchar(x % 10 + '0');
    }
    
    void fsh() {
        fwrite(buf, 1, pp - buf, stdout);
        pp = buf;
    }
    
    template <typename T>
    inline void write(T x, char ch = 0) {
        print(x);
        if (ch != 0) Putchar(ch);
        fsh();
    }

} using namespace Fwrite;

inline void inc(ll &x, ll y) {
    x += y;
    if (x >= P) x -= P;
}

int main() {
    #ifndef ONLINE_JUDGE
        freopen("sample.in", "r", stdin);
    #endif

    read(testCase);
    for (int _ = 1; _ <= testCase; ++_) {
        read(n), read(P);
        for (int i = 0; i <= n; i++) ans[i] = ans1[i] = ans2[i] = 0;
        for (int i = 0; i <= n; i++)
            for (int j = 0; j <= n; j++)
                f[i][j] = g[i][j] = 0;

        if (P == 1) {
            printf("Case #%d:\n", _);
            for (int i = 1; i <= n; i++)
                printf("%lld%c", ans[i], " \n"[i == n]);
            continue;
        }
        
        // for (int i = 0; i <= n; i++) g[0][i] = 1;
        f[0][0] = f[1][1] = 1;
        for (int i = 1; i < n; i++)
            for (int j = 1; j <= n; j++) {
                if (j < n) inc(f[i + 1][j + 1], f[i][j]);
                inc(f[i + 1][j], f[i][j] * j % P);
            }

        for (int i = 0; i <= n; i++) g[0][i] = 1;
        g[1][n] = n % P;
        for (int i = 1; i < n; i++) g[1][i] = (i + 1) % P;
        for (int i = 2; i <= n; i++)
            for (int j = 0; j <= n; j++) {
                inc(g[i][j], g[i - 1][j] * j % P);
                if (j < n) inc(g[i][j], g[i - 1][j + 1]);
            }
        
        // for (int i = 0; i <= n; i++) {
        //     for (int j = 0; j <= n; j++) printf("%lld ", f[i][j]);
        //     printf("\n");
        // }
        // printf("\n");
        // for (int i = 0; i <= n; i++) {
        //     for (int j = 0; j <= n; j++) printf("%lld ", g[i][j]);
        //     printf("\n");
        // }

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                sum1[j] = sum1[j - 1];
                if (n - j - 2 >= 0) inc(sum1[j], f[j - 1][i - 1] * g[n - j - 2][i] % P);
                sum2[j] = sum2[j - 1];
                if (n - j - 1 >= 0) inc(sum2[j], f[j - 1][i - 1] * g[n - j - 1][i] % P);

                inc(ans1[i], 1LL * (n - j) * sum1[j - 1] % P);
                if (n - j - 1 >= 0) inc(ans1[i], f[j - 1][i - 1] * g[n - j - 1][i] % P * (n - j) % P);
                inc(ans2[i], sum2[j - 1]);
                inc(ans2[i], f[j - 1][i - 1] * g[n - j][i] % P);
            }

            ans[i] = ans2[i];
            inc(ans[i], ans1[i]), inc(ans[i], ans1[i]);
        }

        // for (int i = 1; i <= n; i++)
        //     printf("%lld ", ans1[i]);
        // printf("\n");
        // for (int i = 1; i <= n; i++)
        //     printf("%lld ", ans2[i]);
        // printf("\n");

        // printf("Case #%d:\n", _);
        // for (int i = 1; i <= n; i++)
        //     printf("%lld%c", ans[i], " \n"[i == n]);
        printf("Case #"), write(_, ':'), puts("");
        for (int i = 1; i <= n; i++)
            write(ans[i], " \n"[i == n]);
    }
    return 0;
}
View Code

 

时间复杂度$O(n^2)$,要注意常数。

 

posted @ 2020-10-28 18:59  CzxingcHen  阅读(270)  评论(0编辑  收藏  举报