tc633div1 550分


题目描述:

给定原来由一些1~D面值的硬币凑出0~D面值的方案数模1e9+7,询问q次,每次询问移出number个value面值的硬币后凑出D面值的方案数。
每个硬币包括面值一样的硬币都是不同的.
要求q*D的时间复杂度.

题解:

对于每一个询问,需要d的复杂度算出来dp[d]. 先考虑简单的递推,如果number=1,那么其实dp[d] = f[d]-dp[d-1] 就行了. 这样dp过去.但是如果number变大,每次都要往前边搞一个组合数,就成d*d的了. 这样不行.然后想要维护带组合数权值的前缀和,发现不会. 既然这样,我们只用算dp[d]就行了. 能不能推出dp[d]和初始值f之间的关系,就是不用dp[d-1]. 这样去写公式,发现又是组合数一堆. 但是遇到这种情况我们以后都这样处理:组合数的公式其实可以一次一次的差分求和搞出来. 我们每次只去掉一个硬币,那么dp[d] = dp[d-1] + 上一层的dp[d]. 只看比如f0的系数的绝对值,发现其实是这一层d-1的值加上上一层d的值组成这一层d的值,就是说把上一层当成了差分!!! 每一层f0的初始值都是1, 这样 差分一次就是111111
差分两次就是12345 差分三次就是1 3 6… 所以每次f0的系数权值都是:C[number-1+d][number-1], 并且f1对dp[d]的贡献度其实是f0的前一项. 这样就直接算出了每一个的权值,就可以o(d)的计算了.
上面是value=1的分析. 如果value = 2 …, 我们发现和d有关的f只有f[d],f[d-value], f[d-2*value]…. 先把这些数筛出来,然后就和上面的一摸一样了.

以上是差分,思路特别顺,就是为了找每一项的权值,发现有组合数不好弄,于是先算number=1哪一层的,发现有差分的性质,写一写就得出结论.

下面是另一种思路:
每个询问的生成函数多项式可以看做是对原来的多项式除一个(1+x^value)^number,也就是乘以(1+x^value)^(-number).之后求x^d的系数就行了. 涉及到展开C[-number][1], C[-number][2] . C[-n][k] = (-1)^k*C[k+n-1][k] = (-1)^k*C[k+n-1][n-1]. 这样算出来每一个的系数是C[一个数][number-1], 正负和k有关, 然后value不是1的时候之和d-value整数倍的f有关.

重点:

(1)最关键的.首先dp时间太多不能推过去要去想权值前缀和,发现组合数权值不能求, 那么一定要转化成用最初的f来直接表示dp[d], 现在使用推公式技巧,假设number=1, 那么发现i位置f0的权值是由i-1的权值加上上一次i位置f0的权值得到的. 并且最开始差分是0, 然后是 1,然后就是累加的了. 于是观察规律就能够得到.
(2)转化:当value不是1的时候,挑出来会影响d的值,之后转化成和以前一样.
(3)用母函数的方法来表示 和等于 多少 的种类数. 用来推导公式. 并且用到负数组合数的公式.

代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <ctype.h>
#include <limits.h>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <queue>
#include <map>
#include <stack>
#include <set>
#include <bitset>
#define CLR(a) memset(a, 0, sizeof(a))
#define REP(i, a, b) for(int i = a;i < b;i++)
#define REP_D(i, a, b) for(int i = a;i <= b;i++)

typedef long long ll;

using namespace std;

const int MOD = 1e9+7.1;
const int maxn = 3000 + 10;
int dp[maxn];

int jcrev[maxn];
int jc[maxn];

class ChangingChange
{
public:
    int pow_mod(int x, int n)
    {
        int res = 1;
        int temp = x%MOD;
        while(n)
        {
            if(n&1)
            {
                res = (res*temp)%MOD;
                temp = (temp*temp)%MOD;
                n >>= 1;
            }
        }
        return res;
    }
public:
    void getJc()
    {
        jcrev[0] = pow_mod(1, MOD-2);
        jc[0] = 1;
        for(int i = 1; i<=1000000; i++)
        {
            jcrev[i] = (jcrev[i-1]*pow_mod(i, MOD-2))%MOD;
            jc[i] = (jc[i-1]*i)%MOD;
        }
    }
public:
    int C(int n, int m)
    {
        int t = ((jc[n]*jcrev[m]%MOD)*jcrev[n-m])%MOD;
        return t;
    }

public:
    vector <int> countWays(vector <int> ways, vector <int> valueRemoved, vector <int> numRemoved)
    {
        vector<int> res;
        int d = ways.size()-1;
        getJc();
        for(int i = 0;i<valueRemoved.size();i++)
        {
            vector<int> f;
            int x = valueRemoved[i], num = numRemoved[i];
            int start = d - d/x*x;
            for(int i = start;i<=d;i += x)
            {
                f.push_back(ways[i]);
            }
            int ans = f[f.size()-1];
            int k = num - 1;
            int now = k;
            int fuhao = -1;
            for(i = f.size()-2;i>=0;i--)
            {
                ans = ((ans+fuhao*C(now, k))%MOD + MOD)%MOD;
            }
            res.push_back(ans);
        }
    }
};

int main()
{
    freopen("2Bin.txt", "r", stdin);
    //freopen("3Bout.txt", "w", stdout);

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值