【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
",ans);
30     }
31     return 0;
32 }
View Code
原文地址:https://www.cnblogs.com/cdcq/p/12656285.html