第一眼生成函数。四个等比数列形式的多项式相乘,可以化成四个分式。其中分母部分是固定的,可以多项式求逆预处理出来。而分子部分由于项数很少,询问时2^4算一下贡献就好了。这个思路比较直观。只是常数巨大,以及需要敲一发类似任意模数ntt的东西来避免爆精度。成功以这种做法拿下luogu倒数rank1,至于bzoj不指望能过了。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<iomanip>
using namespace std;
int read()
{
int x=,f=;char c=getchar();
while (c<''||c>'') {if (c=='-') f=-;c=getchar();}
while (c>=''&&c<='') x=(x<<)+(x<<)+(c^),c=getchar();
return x*f;
}
#define N 550000
#define T 100000
#define P1 998244353
#define P2 1004535809
int r[N],c1,c2,c3,c4,tot,d1,d2,d3,d4,s,t;
int a[N],b[N],c[N],e[][N];
long long f[N];
void inc(int &x,int P){x++;if (x>=P) x-=P;}
void dec(int &x,int P){x--;if (x<) x+=P;}
int ksm(int a,int k,int P)
{
if (a==) return ;
if (k==) return ;
int tmp=ksm(a,k>>,P);
if (k&) return 1ll*tmp*tmp%P*a%P;
else return 1ll*tmp*tmp%P;
}
long long ksc(long long a,long long b,long long P)
{
long long t=a*b-(long long)((long double)a*b/P+0.5)*P;
return t<?t+P:t;
}
void DFT(int n,int *a,int p,int P)
{
for (int i=;i<n;i++) if (i<r[i]) swap(a[i],a[r[i]]);
for (register int i=;i<=n;i<<=)
{
int wn=ksm(p,(P-)/i,P);
for (register int j=;j<n;j+=i)
{
int w=;
for (register int k=j;k<j+(i>>);k++,w=1ll*w*wn%P)
{
int x=a[k],y=1ll*w*a[k+(i>>)]%P;
a[k]=(x+y)%P,a[k+(i>>)]=(x-y+P)%P;
}
}
}
}
void mul(int n,int *a,int *b,int P,int inv3)
{
DFT(n,a,,P),DFT(n,b,,P);
for (int i=;i<n;i++) a[i]=1ll*a[i]*(P+-1ll*a[i]*b[i]%P)%P;
DFT(n,a,inv3,P);
int inv=ksm(n,P-,P);
for (int i=;i<n;i++) a[i]=1ll*a[i]*inv%P;
}
void solve(int P,int inv3,int op)
{
memset(a,,sizeof(a));
memset(b,,sizeof(b));
memset(c,,sizeof(c));
if (c1+c2+c3+c4<=T) inc(a[c1+c2+c3+c4],P);
if (c1+c2+c3<=T) dec(a[c1+c2+c3],P);
if (c1+c2+c4<=T) dec(a[c1+c2+c4],P);
if (c4+c2+c3<=T) dec(a[c4+c2+c3],P);
if (c1+c4+c3<=T) dec(a[c1+c4+c3],P);
if (c1+c2<=T) inc(a[c1+c2],P);
if (c1+c3<=T) inc(a[c1+c3],P);
if (c1+c4<=T) inc(a[c1+c4],P);
if (c2+c3<=T) inc(a[c2+c3],P);
if (c4+c2<=T) inc(a[c4+c2],P);
if (c3+c4<=T) inc(a[c3+c4],P);
dec(a[c1],P);dec(a[c2],P);dec(a[c3],P);dec(a[c4],P);
inc(a[],P);
t=;b[]=;
while (t<=T)
{
t<<=;
for (int i=;i<t;i++) c[i]=a[i];
for (int i=;i<(t<<);i++) r[i]=(r[i>>]>>)|(i&)*t;
mul(t<<,b,c,P,inv3);
for (int i=t;i<(t<<);i++) b[i]=;
}
memcpy(e[op],b,sizeof(e[op]));
}
void crt()
{
long long P=1ll*P1*P2,inv1=ksm(P2%P1,P1-,P1),inv2=ksm(P1%P2,P2-,P2);
for (int i=;i<=T;i++)
f[i]=(ksc(1ll*e[][i]*P2%P,inv1,P)+ksc(1ll*e[][i]*P1%P,inv2,P))%P;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("bzoj1042.in","r",stdin);
freopen("bzoj1042.out","w",stdout);
const char LL[]="%I64d\n";
#else
const char LL[]="%lld\n";
#endif
c1=read(),c2=read(),c3=read(),c4=read(),tot=read();
solve(P1,,);
solve(P2,,);
crt();
while (tot--)
{
d1=read(),d2=read(),d3=read(),d4=read(),s=read();
d1=min(1ll*s+,1ll*(d1+)*c1);
d2=min(1ll*s+,1ll*(d2+)*c2);
d3=min(1ll*s+,1ll*(d3+)*c3);
d4=min(1ll*s+,1ll*(d4+)*c4);
long long ans=f[s];
if (d1+d2+d3+d4<=s) ans+=f[s-(d1+d2+d3+d4)];
if (d1+d2+d3<=s) ans-=f[s-(d1+d2+d3)];
if (d1+d2+d4<=s) ans-=f[s-(d1+d2+d4)];
if (d4+d2+d3<=s) ans-=f[s-(d4+d2+d3)];
if (d1+d4+d3<=s) ans-=f[s-(d1+d4+d3)];
if (d1+d2<=s) ans+=f[s-(d1+d2)];
if (d1+d3<=s) ans+=f[s-(d1+d3)];
if (d1+d4<=s) ans+=f[s-(d1+d4)];
if (d2+d3<=s) ans+=f[s-(d2+d3)];
if (d4+d2<=s) ans+=f[s-(d4+d2)];
if (d3+d4<=s) ans+=f[s-(d3+d4)];
if (d1<=s) ans-=f[s-d1];
if (d2<=s) ans-=f[s-d2];
if (d3<=s) ans-=f[s-d3];
if (d4<=s) ans-=f[s-d4];
printf(LL,ans);
}
return ;
}

  还有一种更优秀的做法。考虑如果硬币没有个数限制的话,就是一个完全背包。添加限制可以想到容斥。我们枚举有哪几种硬币超过了个数限制,就可以容斥斥斥容容容斥把多重背包转化成完全背包了。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<iomanip>
using namespace std;
int read()
{
int x=,f=;char c=getchar();
while (c<''||c>'') {if (c=='-') f=-;c=getchar();}
while (c>=''&&c<='') x=(x<<)+(x<<)+(c^),c=getchar();
return x*f;
}
#define N 100010
#define ll long long
int c[],t,d[],s;
ll f[N],ans;
int calc(int k,int x){if (k<x) return ;else return f[k-x];}
void dfs(int k,int sum,ll tot)
{
if (tot>s) return;
if (k==) {ans+=((sum&)?-:)*f[s-tot];return;}
dfs(k+,sum+,tot+1ll*(d[k]+)*c[k]);
dfs(k+,sum,tot);
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("bzoj1042.in","r",stdin);
freopen("bzoj1042.out","w",stdout);
const char LL[]="%I64d\n";
#else
const char LL[]="%lld\n";
#endif
for (int i=;i<;i++) c[i]=read();
t=read();
f[]=;
for (int i=;i<;i++)
for (int j=c[i];j<=N-;j++)
f[j]+=f[j-c[i]];
while (t--)
{
for (int i=;i<;i++) d[i]=read();
s=read();
ans=;
dfs(,,);
printf(LL,ans);
}
return ;
}

  仔细考虑一下会发现两个做法本质上其实是一样的。分子部分所乘的多项式就是一个容斥的过程,而求逆所得的结果就是完全背包。

BZOJ1042 HAOI2008硬币购物(任意模数NTT+多项式求逆+生成函数/容斥原理+动态规划)的更多相关文章

  1. 【bzoj3456】城市规划 容斥原理+NTT+多项式求逆

    题目描述 求出n个点的简单(无重边无自环)无向连通图数目mod 1004535809(479 * 2 ^ 21 + 1). 输入 仅一行一个整数n(<=130000) 输出 仅一行一个整数, 为 ...

  2. BZOJ1042 [HAOI2008]硬币购物 【完全背包 + 容斥】

    1042: [HAOI2008]硬币购物 Time Limit: 10 Sec  Memory Limit: 162 MB Submit: 2924  Solved: 1802 [Submit][St ...

  3. 【BZOJ 3456】 3456: 城市规划 (NTT+多项式求逆)

    3456: 城市规划 Time Limit: 40 Sec  Memory Limit: 256 MBSubmit: 658  Solved: 364 Description 刚刚解决完电力网络的问题 ...

  4. NTT+多项式求逆+多项式开方(BZOJ3625)

    定义多项式$h(x)$的每一项系数$h_i$,为i在c[1]~c[n]中的出现次数. 定义多项式$f(x)$的每一项系数$f_i$,为权值为i的方案数. 通过简单的分析我们可以发现:$f(x)=\fr ...

  5. BZOJ 4555 [Tjoi2016&Heoi2016]求和 ——分治 NTT 多项式求逆

    不想多说了,看网上的题解吧,我大概说下思路. 首先考察Stirling的意义,然后求出递推式,变成卷积的形式. 然后发现贡献是一定的,我们可以分治+NTT. 也可以直接求逆(我不会啊啊啊啊啊) #in ...

  6. BZOJ 3456 城市规划 ( NTT + 多项式求逆 )

    题目链接: https://www.lydsy.com/JudgeOnline/problem.php?id=3456 题意: 求出\(n\)个点的简单(无重边无自环)无向连通图的个数.(\(n< ...

  7. [BZOJ3456]城市规划:DP+NTT+多项式求逆

    写在前面的话 昨天听吕老板讲课,数数题感觉十分的神仙. 于是,ErkkiErkko这个小蒟蒻也要去学数数题了. 分析 Miskcoo orz 带标号无向连通图计数. \(f(x)\)表示\(x\)个点 ...

  8. P4233-射命丸文的笔记【NTT,多项式求逆】

    正题 题目链接:https://www.luogu.com.cn/problem/P4233 题目大意 随机选择一条有哈密顿回路的\(n\)个点的竞赛图,求选出图的哈密顿回路的期望个数. 对于每个\( ...

  9. BZOJ1042 [HAOI2008]硬币购物 完全背包 容斥原理

    欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目传送门 - BZOJ1042 题目概括 硬币购物一共有4种硬币.面值分别为c1,c2,c3,c4.某人去商店买东西,去了t ...

随机推荐

  1. Android学习之基础知识十一 —运用手机多媒体

    一.使用通知(Notification) 通知(Notification)是Android系统中比较有特色的一个功能,当某个应用程序希望向用户发出一些提示信息,而该应用程序又不在前台运行时,就可以借助 ...

  2. Caused by: java.lang.IllegalStateException: commit already called

    出现的情况,当FragmentTransaction是全局变量时,fragment智能调用一次commint: 异常的意思:commint已经被调用过了. 所以不要重复调用它就好了.

  3. java 之UDP编程

    大白话:每一台电脑都有自己的ip地址,向指定的ip地址发数据,数据就发送到了指定的电脑.UDP通信只是一种通信方式而已,其特点就不多说.有了ip地址数据就能发送到指定的电脑了,但是呢!我把数据发送到电 ...

  4. [01] 浅谈RESTful风格的API

    1.什么是RESTful风格的API REST,即Representational State Transfer,可以理解为"(资源的)表现层状态转化". 在网络上,我们通过浏览器 ...

  5. TCP/IP协议---UDP协议

    UDP是一个简单的面向数据报的运输层协议:进程的每个输出操作都产生一个UDP数据报,并组装成一份待发送的IP数据报.UDP数据报是要依赖IP数据报传送的.UDP协议并不可靠,它不能保证发出去的包会被目 ...

  6. Zookeeper-基本概念

    ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务.它是一个典型的分布式数据一致性的解决方案,分布式应用可以基于它实现诸如数据发布/订阅.负载均衡.命名服务.分布式协调/通知.集群管理 ...

  7. Scala学习(六)---Scala对象

    Scala中的对象 摘要: 在本篇中,你将会学到何时使用Scala的object语法结构.在你需要某个类的单个实例时,或者想为其他值或函数找一个可以挂靠的地方时,你就会用到它.本篇的要点包括: 1. ...

  8. (11)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- Thrift高效通讯 (完结)

    一. 什么是 RPC Restful 采用 Http 进行通讯,优点是开放.标准.简单.兼容性升级容易: 缺点是性能略低.在 QPS 高或者对响应时间要求苛刻的服务上,可以用 RPC(Remote P ...

  9. HTML 5 拖放

    拖放(Drag 和 drop)是 HTML5 标准的组成部分. 拖放 拖放是一种常见的特性,即抓取对象以后拖到另一个位置. 在 HTML5 中,拖放是标准的一部分,任何元素都能够拖放. 拖放事件 1. ...

  10. Linux实践:模块

    标签(空格分隔): 20135321余佳源 一.实践原理 Linux模块是一些可以作为独立程序来编译的函数和数据类型的集合.之所以提供模块机制,是因为Linux本身是一个单内核.单内核由于所有内容都集 ...