bzoj4588 CoinChange

http://www.elijahqi.win/archives/2876
Description
Claris王国有n种货币,每种货币有一个价值vi,并且满足任意两种货币的价值成倍数关系。
即对于第i种货币和第j种货币,有vi整除vj,或者vj整除vi。
现在给出这n种货币的价值,请你计算有多少种方案能凑出价值为m的货币组合。
假设每种货币的数量是无限的,货币的价值互不相同。
为了保证有解,我们约定存在一种货币的价值为1。
由于答案可能很大,你只需要给出答案对10^9+7取模的值。
Input
对于每组数据:
第一行两个正整数n和m。
第二行n个正整数,表示n种货币的价值,保证货币的价值是按照升序给出的。
每组数据有n<=40, m<=10^18, vi<=10^18。
不超过300组数据。
Output
Sample Input
2 4
1 3
3 4
1 2 4
Sample Output
2
4
Hint
对于第一组样例,两种方案分别为1+1+1+1和1+3。
对于第二组样例,四种方案分别为1+1+1+1和1+1+2和2+2和4。
HINT
Source
Topcoder SRM 527 Div1 Hard P8XCoinChange By Tangjz
先将所有硬币按照面值从小到大排序考虑从小到大取钱 考虑前i-1种面值已经取完
设f[i][j]表示用前i种硬币凑成m%w[i]+j*w[i]的方案数
现在考虑这个状态如何得到的 可以由前i-1种硬币凑成的方法转移得到 转移的时候枚举i这种硬币用了多少个即可 那么枚举i这种硬币用了几个 即可得到我用i这种硬币的方案 比如求我这种硬币用了j个的时候可以依次通过枚举前面的硬币凑成0~j种情况来获得 可以发现这里的每个f都是一个i次多项式 所以考虑拉格朗日插值 只需要保留i+1个值即可 那么 即可通过拉格朗日插值得到后面的答案 刚刚的转移 举例:显然可以由m%w[i-1]+k*a*w[i-1]获得 a=w[i]/w[i-1] k=0的时候我实际上插值获得的是b*w[i-1]+ 上一个多项式的方案数就是相当于余数这一项始终是带着走的

这题还需要注意一点就是我插值的那一项有可能要插一个负的值 那就是说明当前我的w[i]太大了但是因为m总共只有这么多 所以这一项如果插不满也没有关系

#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
#define ll long long
#define N 55
#define mod 1000000007 
using namespace std;
inline char gc(){
    static char now[1<<16],*S,*T;
    if (T==S){T=(S=now)+fread(now,1,1<<16,stdin);if (T==S) return EOF;}
    return *S++;
}
template<class T>
inline bool read(T &x){
    char ch=gc();if (ch==EOF) return 0;
    while(!isdigit(ch)) {ch=gc();}
    while(isdigit(ch)) x=x*10+ch-'0',ch=gc();
    return 1;
}
inline ll read(){
    ll x=0;char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x;
}
int inv[N],g[N];
template<class T>
inline void add(T &x,int ad){x+=ad;if (x>=mod) x-=mod;}
inline int ksm(int b,int t){
    int tmp=1;for (;t;b=(ll)b*b%mod,t>>=1) if (t&1) tmp=(ll)tmp*b%mod;return tmp;
}
struct poly{
    ll w;int y[N],pre[N],succ[N],num[N],n;
    inline void init(){
        for (int i=0;i<=n;++i)
            num[i]=(ll)inv[i]*((n-i&1)?(mod-inv[n-i]):inv[n-i])%mod*y[i]%mod;
        pre[0]=succ[n+2]=1;
    }
    inline int getval(ll x){
        x/=w;if (x<=n) return y[x];x%=mod;int tmp=0;
        for (int i=1;i<=n+1;++i) pre[i]=(ll)pre[i-1]*(x-i+1)%mod;
        for (int i=n+1;i;--i) succ[i]=(ll)succ[i+1]*(x-i+1)%mod;
        for (int i=0;i<=n;++i) tmp=((ll)tmp+(ll)pre[i]*succ[i+2]%mod*num[i]%mod+mod)%mod;
        return tmp;
    }
}f[N];
int n;ll m;int tmp[N];
int main(){
    //freopen("bzoj4588.in","r",stdin);
    //freopen("coin.out","w",stdout);
    g[0]=1;for (int i=1;i<=40;++i) g[i]=(ll)g[i-1]*i%mod;
    inv[40]=ksm(g[40],mod-2);
    for (int i=39;~i;--i) inv[i]=(ll)inv[i+1]*(i+1)%mod;
    while(~scanf("%d%lld",&n,&m)){
        for (int i=1;i<=n;++i) f[i].n=i,f[i].w=read(),memset(f[i].y,0,sizeof(f[i].y));
        f[1].y[1]=f[1].y[0]=1;
        for (int i=2;i<=n;++i){
            ll temp=m%f[i].w;f[i-1].init();
            for (int j=0;j<=i;++j) {
                tmp[j]=f[i-1].getval(temp);
                if (temp>m) break;temp+=f[i].w;
            }
            for (int j=0;j<=i;++j)
                for (int k=0;k<=j;++k) add(f[i].y[j],tmp[k]);
        }f[n].init();printf("%d\n",f[n].getval(m));n=0;m=0;
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值