F - Red-White Fence

选出一个山峰一样的数列,问总长度是多少,我们发现,周长的总长只取决于最长的那一块木板和选取的模板个数,总周长就是 ( L + M ) ∗ 2 ( L + M ) * 2 (L+M)2

其次,我们考虑给木板按长度分类

第一类是对应长度只有一块的,那么这个木板可以放在左边,也可以放在边,对应的次数就是 a i = C ( s a , i ) ∗ 2 i a_i = C(sa,i) * 2^i ai=C(sa,i)2i

第二类就是对应长度有两块以上的,我们取出两块,标记一块放在左边,一块是放在右边的,那么对应的次数就是 b i = C ( s b , i ) b_i = C(sb,i) bi=C(sb,i)

因此答案就是 a i , b i a_i,b_i ai,bi 的卷积,因为两个序列可以拼起来拼成一个,那么FFT求一次卷积即可

#include 
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
int Gcd(int a,int b){if (b == 0) return a; return Gcd(b , a%b);}
int Lcm(int a, int b){ return a/Gcd(a,b)*b;}
inline long long read(){
   long long f = 1, x = 0;char ch = getchar();
    while (ch > '9' || ch < '0'){if (ch == '-')f = -f;ch = getchar();}
    while (ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
    return x * f;
}
const int maxn = 2e6 + 10;
const double pi = acos(-1.0);
// const LL P = 2281701377;
const LL P = 998244353;
const LL mod = P;
const LL G = 3;
int n,m,Lim = 1,l,r[maxn];
LL a[maxn],b[maxn],L1[maxn],ans[maxn];
int cnt[maxn];
LL qpow(LL a,LL b){
    LL base = a % P,ans = 1;
    while(b){
        if (b & 1) ans = ans*base % P;
        base = base * base % P;
        b >>= 1;
    }
    return ans % P;
}
inline void NTT(LL *A,int type){
    for(int i=0; i; i++) 
        if (i < r[i]) swap(A[i],A[r[i]]);
    for(int m = 1; m < Lim; m<<=1){
        LL wn = qpow(G, (P-1)/(m << 1));
        for(int j=0; j; j+=(m<<1)){
            LL w = 1;
            for(int k=0; k; k++,w = w*wn % P){
                LL x = A[j+k],y = w*A[j+k+m] % P;
                A[j+k] = (x + y) % P;
                A[j+k+m] = (x - y + P) % P;
            }
        }
    }
    if (type == 1) return;
    LL inv = qpow(Lim,P-2); reverse(a+1,a+Lim);
    for(int i=0; i<=Lim; i++) a[i] = 1ll*a[i]*inv % P;
}
void init(int n,int m){
    Lim = 1;
    l = 0;
    memset(r,0,sizeof(r));
    while(Lim <= n + m) { Lim <<= 1; l++;} // 不懂,反正加上就对了
    for(int i=0; i<=Lim; i++) r[i] = ( r[i>>1]>>1 )| ( (i&1)<<(l-1) );// 不懂,反正加上就对了z
}
void mul(LL* a,int n, LL* b,int m){
    for(int i=n+1; i<=Lim; i++) a[i] = 0;
    for(int i=m+1; i<=Lim; i++) b[i] = 0;
    for(int i=0; i<=n; i++) a[i] = (a[i]%P + P) % P; // 第一个多项式系数
    for(int i=0; i<=m; i++) b[i] = (b[i]%P + P) % P; // 第二个多项式系数
    NTT(a,1); NTT(b,1);
    for(int i=0; i<= Lim; i++) a[i] = a[i]*b[i] % P; // O(n)求点值表示法
    NTT(a,-1); // 逆变换变成系数表示法
}
LL C(LL n,LL m){
    static LL M = 0,inv[maxn],mul[maxn],invMul[maxn];
    while(M <= n){
        if (M){
            inv[M] = M == 1 ? 1 : (mod-mod/M)*inv[mod%M]%mod;
            mul[M] = mul[M-1]*M%mod;
            invMul[M] = invMul[M-1]*inv[M] % mod;
        }else{
            mul[M] = 1;
            invMul[M] = 1;
        }
        M++;
    }
    return mul[n]*invMul[m] % mod * invMul[n-m]%mod;
}
int main(){
    int n = read(),k = read();
    for(int i=1; i<=n; i++){
        L1[i] = read();
    }
    for(int i=1; i<=k; i++){
        int L = read();
        memset(cnt,0,sizeof(cnt));
        for(int i=1; i<=n; i++){
            if (L1[i] < L){
                cnt[L1[i]]++;
            }
        }
        int sa = 0,sb = 0;
        for(int i=1; i; i++){
            if (cnt[i] >= 2) sb += 2;
            else if (cnt[i] == 1) sa++;
        }
        for(int i=0; i<=sa; i++) a[i] = C(sa,i) * qpow(2,i);
        for(int i=0; i<=sb; i++) b[i] = C(sb,i);
        init(sa,sb);
        mul(a,sa,b,sb);
        for(int i=0; i<=Lim; i++){
            ans[L + i + 1] += a[i];
            ans[L + i + 1] %= mod;
        }
    }
    int q = read();
    while(q--){
        int x = read();
        printf("%lld\n",ans[x / 2]);
    }
    return 0;
}   

你可能感兴趣的:(Codeforce练习)