Red-White Fence(计数问题,FFT/NTT)

描述

一个围栏由一个红板和几块(可能为0)块白板组成。红板是最长的一块,并且左边的白板长度严格递增,右边严格递减。
这些板子从高度为0,从左到右连续紧密放置,组成一个多边形。
给出询问q,每个给出参数Q,满足多边形周长是Q的板子放置的方案数(相同位置长度相同视作一种方案)。

链接


分析

首先来考虑一下围栏的周长是多少,假设围栏由 m m m 块白板和 1 块长度为 L L L 的红板组成,那么很容易发现这样的围栏周长 P = 2 ∗ ( L + m + 1 ) P = 2*(L+m+1) P=2(L+m+1)​。

现在来考虑用一块长度为 L L L​ 的红板,能够组成周长为 P P P​ 的围栏的方案数有多少。由之前推得得式子,要组成这样得围栏,需要的白板个数就是 P 2 − L − 1 \frac{P}{2}-L-1 2PL1​。

因为红板得数量最多只有 5,所以迭代枚举红板的长度 L L L,并且对每个询问的周长 Q i Q_i Qi,计算用长度为 L L L 的红板与 Q i 2 − L − 1 \frac{Q_i}{2}-L-1 2QiL1​ 块白板组成围栏的方案数即可。

现在的问题是,在长度小于红板长度 L L L 的白板中,用 k k k​ 块与红板组成围栏得方案数是多少?

首先假设有 n n n​ 块长度小于 L L L 的白板,并且每种长度只有一个白板,即 n n n​ 块白板长度互不相同。这种情况,首先是要从 n n n 个白板中选 k k k 个出来: C n k C_n^k Cnk,然后对于每块白板,它可以选择放在红板左边或者右边: 2 k 2^k 2k。因此方案数是 C n k 2 k C_n^k2^k Cnk2k​。

然后再来考虑一下相反情况,在长度小于红板长度的白板中,对于这些白板中的每种长度,都至少有两块白板是这种长度的。假设有 m m m​ 种不同长度的白板小于红板,每种长度白板,我们其实是有四种选择,1.不选这种长度的白板,2.选1个这种长度的白板并放在红板左侧,3.选1个这种长度的白板并放在红板右侧,4. 选 2 个这种长度的白板分别放在红板左侧右侧。因此可以这样考虑,把 m m m​​​​​ 种白板中的每一种白板拆成 2 个点,分别表示选这种白板并放在左侧,与选这种白板并放在右侧。那这种情况最后的方案数就是 C 2 m k C_{2m}^k C2mk​(挺巧妙的)。

接下来就是综合考虑上面的两种情况了,既有长度唯一的白板,又有长度不唯一的白板。那么考虑枚举选取几个长度唯一的白板就容易得出答案为 ∑ i = 0 k C 2 m k − i C n i 2 i \sum_{i=0}^{k}C_{2m}^{k-i}C_n^i2^i i=0kC2mkiCni2i​。

至此,按照这个过程求解的时间复杂度是 O ( k q n ) O(kqn) O(kqn)​​,是会超时的。

接下来就是类似于整数拆分的技巧,用多项式优化。

对两个多项式 ∑ i = 0 n C n i 2 i x i \sum_{i=0}^{n}C_n^i2^ix^i i=0nCni2ixi​​ 和 ∑ i = 0 2 m C 2 m i x i \sum_{i=0}^{2m}C_{2m}^ix^i i=02mC2mixi​​​ 卷积一下即可,选 k k k​​ 个白板的答案就是结果多项式中 x k x_k xk​​​​ 的系数。用 F F T FFT FFT​​ 或者 N T T NTT NTT​​ 都可以求卷积,但是本题的模数特殊,用 N T T NTT NTT​​​ 更适合一些。

最终时间复杂度是 O (   k ( n l o g n + q )   ) O(\ k(nlogn+q)\ ) O( k(nlogn+q) )​。​

( 注意:时间复杂度中的 k , q , n k,q,n k,q,n​​ 是题面中的变量,并非题解中指代的变量 )

代码

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int N = 300010;
const int mod = 998244353;
const int G = 3;

int a[N], b[N], Q[N], ans[N], fact[N], rfact[N];
int p1[N << 2], p2[N << 2], rev[N << 2];

int qmi(int a, int b, int p)
{
    int ans = 1;
    for (; b; b >>= 1)
    {
        if (b & 1) ans = (ll)ans * a % mod;
        a = (ll)a * a % mod;
    }
    return ans;
}

int C(int n, int m)
{
    if (n < m) return 0;
    return (ll)fact[n] * rfact[m] % mod * rfact[n - m] % mod;
}

void getpoly(int a[], int n, int r)
{
    for (int i = 0; i <= n; i++)
        a[i] = (ll)C(n, i) * qmi(r, i, mod) % mod;
}

void ntt(int a[], int tot, int sgn, int p)
{
    for (int i = 0; i < tot; i++)
        if (i < rev[i])
            swap(a[i], a[rev[i]]);
    int g = sgn == 1 ? G : qmi(G, p - 2, p);
    for (int mid = 1, t = 1; mid < tot; mid <<= 1, t++)
    {
        int wn = qmi(g, (p - 1) >> t, p);
        for (int i = 0; i < tot; i += mid << 1)
        {
            int w = 1;
            for (int j = 0; j < mid; j++, w = (ll)w * wn % p)
            {
                int x = a[i + j], y = (ll)w * a[i + mid + j] % p;
                a[i + j] = (x + y) % p;
                a[i + mid + j] = ((x - y) % p + p) % p;
            }
        }
    }
    if (sgn == -1)
    {
        int invtot = qmi(tot, p - 2, p);
        for (int i = 0; i < tot; i++)
            a[i] = (ll)a[i] * invtot % p;
    }
}

void mul_poly(int a[], int n, int b[], int m)
{
    int tot = 1, bit = 0;
    while (tot < n + m + 1) tot <<= 1, ++bit;
    for (int i = 0; i < tot; i++)
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1));

    ntt(a, tot, 1, mod); ntt(b, tot, 1, mod);
    for (int i = 0; i < tot; i++) a[i] = (ll)a[i] * b[i] % mod;
    ntt(a, tot, -1, mod);
}

int main()
{
    fact[0] = fact[1] = rfact[0] = rfact[1] = 1;
    for (int i = 2; i < N; i++) 
    {
        fact[i] = (ll)fact[i - 1] * i % mod;
        rfact[i] = (ll)(mod - mod / i) * rfact[mod % i] % mod;
    }
    for (int i = 2; i < N; i++) rfact[i] = (ll)rfact[i - 1] * rfact[i] % mod;

    int n, k;
    scanf("%d%d", &n, &k);
    map<int, int> mp;
    for (int i = 1; i <= n; i++) 
    {   
        scanf("%d", &a[i]);
        mp[a[i]]++;
    }
    for (int i = 1; i <= k; i++) scanf("%d", &b[i]);
    int q;
    scanf("%d", &q);
    for (int i = 1; i <= q; i++) scanf("%d", &Q[i]);

    for (int i = 1; i <= k; i++)
    {
        int red = b[i];
        int cnt1 = 0, cnt2 = 0;
        for (auto x : mp)
        { 
            if (x.first >= red) break;
            if (x.second == 1) cnt1++;
            else cnt2++;
        }
        memset(p1, 0, sizeof(p1));
        memset(p2, 0, sizeof(p2));
        getpoly(p1, cnt1, 2);
        getpoly(p2, 2 * cnt2, 1);
        mul_poly(p1, cnt1, p2, 2 * cnt2);

        for (int j = 1; j <= q; j++)
        {
            int num = Q[j] / 2 - red - 1;
            if (num >= 0 && num <= cnt1 + 2 * cnt2)
                ans[j] = (ans[j] + p1[num]) % mod;
        }
    }

    for (int i = 1; i <= q; i++)
        printf("%d\n", ans[i]);

    return 0;
}

因为自己也是初学FFT/NTT,在写代码时也遇到了一些的问题,诸如两数相乘忘记开long long,做NTT时误把p1数组写成了a数组,白白浪费了很多时间,所以还需继续努力!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值