[BZOJ 4498] 魔法的碰撞

[BZOJ 4498] 魔法的碰撞_第1张图片

Solution:

刚看到这道题的时候是 codevs群 里评论 思博题…但是我一点想法都没有…。
考虑对于一个给定的的所有 膜法师 (划掉)的排列, 我们的方案数显然是
C(L - w, n) 其中 w = sum ( max(d[i], d[i + 1] ) (i = 0 to n - 1) 。
因为 d 的取值范围实在太小,于是我们考虑如果可以把所有可能的排列的 w 值统计出来,就能很方便的算出总的方案数。
然后下面就是我脑洞大开想出来的神奇的玩意:
我们把所有的 d 值排序,从大到小处理, 考虑动态规划,我们设计状态 f[i][j][k] 表示当前处理到第 i 个 膜法师, 目前的 w 总和 是 j , 有 k 个括号的方案数。

解释一下这个括号 :
开始的时候,我们的序列被一个大括号括起来;

突然,第一个膜法师站了进来!有两种情况:

1 ) 这个最高、跑得最快的膜法师 站到了舞台的一边,然后只允许其他人站在他的同一边,这时候我们的序列可以是 A ( ) 或 ( ) A , 这个膜法师对 w 值的贡献是 d[1];
2 ) 他要站在序列中间, 要求左右都必须有人, 此时我的的序列变成了 () A () ,他对 w 值的贡献是 d[1] * 2 ;

希望你能理解我想表达什么…就是说新加入的人只能加在括号里,可以贴着括号的边(贡献一个 d), 也可以站在括号里把括号分成两个 (贡献两个 d),除了以上两种转移以外还有一种是一个人独站一个括号然后把这个括号删掉。

最后组合数加一加就好辣。

Code

#include <algorithm>
#include <cstring>
#include <cstdlib>
#include <cstdio>

using namespace std;
typedef long long LL;
inline void read(int &x){x=0;char c;while((c=getchar())<'0'||c>'9');for(x=c-'0';(c=getchar())>='0'&&c<='9';x=(x<<1)+(x<<3)+c-'0');}

const int mo = 1e9 + 7;
const int N = 41;

int fac[1000010];
int f[2][3205][41], d[N], m, n, sum;

int ex_gcd(int a, int b, int &x, int &y)
{
    if (!b)
    {
        x = 1, y = 0; return a;
    }
    int d = ex_gcd(b, a % b, y, x);
    y -= x * (a / b);
    return d;
}
int inv(int x)
{
    static int a, b;
    ex_gcd(x, mo, a, b);
    return (a %= mo) < 0 ? a + mo : a;
}
int C(int n, int m)
{
    return (LL) fac[n] * inv(fac[m]) % mo * inv(fac[n - m]) % mo;
}

inline void upd(int &x, int y)
{
    (x += y) >= mo ? x -= mo : 0;
}

int main()
{
    #ifdef LX_JUDGE
    freopen("in.txt", "r", stdin);
    #endif

    read(m), read(n);
    for (int i = 1; i <= n; ++i)
    {
        read(d[i]), --d[i];
        sum += d[i] * 2;
    }

    sort(d + 1, d + n + 1);

    fac[0] = 1;
    for (int i = 1; i <= m; ++i)
        fac[i] = (LL) fac[i - 1] * i % mo;

    int t = 0;
    f[t][0][1] = 1;

    for (int i = n; i; --i)
    {
        memset(f[t ^ 1], 0, sizeof(f[t ^ 1]));
        int v = d[i];
        for (int j = 0; j <= sum; ++j) for (int k = 1; k <= n; ++k) if (f[t][j][k])
        {
            int x = f[t][j][k];
            upd(f[t ^ 1][j + v * 2][k + 1], (LL) x * k % mo);
            upd(f[t ^ 1][j + v][k], (LL) x * k * 2 % mo);
            upd(f[t ^ 1][j][k - 1], (LL) x * k % mo);
        }
        t ^= 1;
    }

    int ret = 0;
    for (int j = min(sum, m - n); ~j; --j) if (f[t][j][0])
        upd(ret, (LL) f[t][j][0] * C(m - j, n) % mo);

    printf("%d\n", ret);

    return 0;
}

你可能感兴趣的:(dp,想法,组合数,bzoj)