BZOJ 3771

题目:

n n n个不同的数,可以取出一个,两个,或三个相加。问各种价值的方案数(顺序不同算一种)。

思路:
A A A为每种物品只选一个的生成函数, B B B为每种物品选两个的生成函数, C C C为每种物品选三个的生成函数。因为算价值的和,所以 x x x的幂表示价值。则

  • 选一个物品的生成函数就是 A A A
  • 选两个不同的物品的生成函数为 A 2 − B 2 \frac{A^2-B}{2} 2A2B A 2 A^2 A2表示任选两个,但是要减掉两个物品相同的情况,即 B B B,然后因为不考虑顺序,除 2 2 2
  • 选三个不同物品的情况的生成函数为 A 3 − 3 A B + 2 C 6 \frac{A^3-3AB+2C}{6} 6A33AB+2C。首先上面已经算出了选两个的情况,所以 A 2 − B 2 ⋅ B \frac{A^2-B}{2}\cdot B 2A2BB就是选了三个,但是会出现 ( X , Y , X ) ( X , Y , Y ) (X,Y,X)(X,Y,Y) (X,Y,X)(X,Y,Y)这两种情况,这两种情况等于 A B − C AB-C ABC,减掉后等于 A 3 − 3 A B + 2 C 2 \frac{A^3-3AB+2C}{2} 2A33AB+2C,再消去顺序的影响除 3 3 3
    综上,最后的生成函数就是 A + A 2 − B 2 + A 3 − 3 A B + 2 C 6 A+\frac{A^2-B}{2}+\frac{A^3-3AB+2C}{6} A+2A2B+6A33AB+2C,答案就是有多少项的系数不为 0 0 0
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
const double pi=acos(-1.0);
const int N = 262194;
int n, m, r[N];
struct complex {
    double x, y;
    complex (double xx = 0, double yy = 0) {
        x = xx, y = yy;
    }
}a[N], b[N], c[N], o[N], a_o[N];
complex operator + (complex a,complex b) { return complex(a.x + b.x, a.y + b.y); }
complex operator - (complex a,complex b) { return complex(a.x - b.x, a.y - b.y); }
complex operator * (complex a,complex b) { return complex(a.x*b.x - a.y*b.y, a.x*b.y + a.y*b.x); }
complex operator / (complex a,double b) { return complex(a.x / b, a.y / b); }
void init(int n) {
    double t = 2 * pi / n;
    for (int i = 0; i <= n; ++i) {
        o[i] = complex(cos(2.0 * pi * i / n), sin(2.0 * pi * i / n));
        a_o[i] = complex(cos(2.0 * pi * i / n),-1 * sin(2.0 * pi * i / n));
    }
}
void fft(int n, complex *a, complex *w)
{
    for (int i = 0; i < n; ++i)
        if (i < r[i])
            swap(a[i], a[r[i]]);
    for (int i = 2; i <= n; i <<= 1) {
        int m = i >> 1;
        for (int j = 0; j < n;j += i)
            for (int k = 0; k < m; ++k) {
                complex t = a[j + m + k] * w[n / i * k];
                a[j + m + k] = a[j + k] - t;
                a[j + k] = a[j + k] + t;
            }
    }
}
main() {
    static int n, m, fn, x, l=0;
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &x);
        a[x].x += 1, b[x * 2].x += 1, c[x * 3].x += 1;
        m = max(m, x * 3);
    }
    fn = 1;
    while (fn <= m + m) fn <<= 1, ++l;
    for (int i = 0; i < fn; ++i)
        r[i] = (r[i >> 1] >> 1) | ((i & 1) << (l - 1));
    init(fn);
    fft(fn, a, o);
    fft(fn, b, o);
    fft(fn, c, o);
    complex n2, n3, n6;
    n2.x = 2, n2.y = 0;
    n3.x = 3, n3.y = 0;
    n6.x = 6, n6.y = 0;
    for (int i = 0; i <= fn; ++i)
        a[i] = ((a[i] * a[i] * a[i]) - n3 * a[i] * b[i] + n2 * c[i]) / 6.0 + (a[i] * a[i] - b[i]) / 2.0 + a[i];
    fft(fn, a, a_o);
    for (int i = 1; i <= m + m; ++i) {
        long long ans = (long long) (a[i].x / fn + 0.5);
        if (ans) printf("%d %d\n", i, ans);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值