【HAOI2008】硬币购物
原题:
c,d,s<=1e5,n<=1000
方法一:
首先可以把题目翻译为数学表达式:
k1c1+k2c2+k3c3+k4c4=s(0<=k1<=d1...)
把它拆成两个
k1c1+k2c2=s1,k3c3+k4c4=s2
这就是两个丢番图方程,可以exgcd解
有解条件分别为gcd(c1,c2)|s1和gcd(c3,c4)|s2
如果d1=gcd(c1,c2),d2=gcd(c3,c4)
那么d1|s1,d2|s2,s=t1d1+t2d2
又是一个丢番图方程,再用exgcd去解它,可以通过通解得到所有可能的s1s2的取值
然后通过s1和s2两个方程的通解得到方案数
虽然理论上复杂度是没问题的,但是TLE了
常数太大……
方法二:
那就容斥吧
需要注意,对于这种多条限制的题目,应当对相反的条件做容斥,再用总方案数减去不合法的方案数
而不是直接对条件做容斥
相反的条件为某些硬币的使用数量大于d
这其实很好求,直接把d+1个硬币花出去,剩下的就是无限制使用硬币的方案数
这其实就是个完全背包问题,别想复杂了
最后需要注意一点
虽然硬币的种类很少,但是还是最好不要写成d1d2d3d4这种自动化差的程序
一时偷懒不想动脑的后果就是对着相似率极高的茫茫代码找bug找到怀疑人生 =_=
代码:
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 int c1,c2,c3,c4,n; 5 int d1,d2,d3,d4,s; 6 long long f[110000]; 7 long long cc(int x){ 8 if(x<0) return 0; 9 return f[x]; 10 } 11 int main(){ 12 cin>>c1>>c2>>c3>>c4>>n; 13 f[0]=1; 14 for(int i=c1;i<=100000;i++) f[i]+=f[i-c1]; 15 for(int i=c2;i<=100000;i++) f[i]+=f[i-c2]; 16 for(int i=c3;i<=100000;i++) f[i]+=f[i-c3]; 17 for(int i=c4;i<=100000;i++) f[i]+=f[i-c4]; 18 while(n --> 0){ 19 scanf("%d%d%d%d%d",&d1,&d2,&d3,&d4,&s); 20 long long ans=cc(s); 21 ans-=cc(s-(d1+1)*c1)+cc(s-(d2+1)*c2)+cc(s-(d3+1)*c3)+cc(s-(d4+1)*c4); 22 ans+=cc(s-(d1+1)*c1-(d2+1)*c2)+cc(s-(d2+1)*c2-(d3+1)*c3)+cc(s-(d3+1)*c3-(d4+1)*c4); 23 ans+=cc(s-(d1+1)*c1-(d3+1)*c3)+cc(s-(d2+1)*c2-(d4+1)*c4)+cc(s-(d1+1)*c1-(d4+1)*c4); 24 ans-=cc(s-(d2+1)*c2-(d3+1)*c3-(d4+1)*c4); 25 ans-=cc(s-(d1+1)*c1-(d3+1)*c3-(d4+1)*c4); 26 ans-=cc(s-(d1+1)*c1-(d2+1)*c2-(d4+1)*c4); 27 ans-=cc(s-(d1+1)*c1-(d2+1)*c2-(d3+1)*c3); 28 ans+=cc(s-(d1+1)*c1-(d2+1)*c2-(d3+1)*c3-(d4+1)*c4); 29 printf("%lld\n",ans); 30 } 31 return 0; 32 }