洛谷P1450 [HAOI2008]硬币购物 动态规划 + 容斥原理

洛谷P1450 [HAOI2008]硬币购物

动态规划 + 容斥原理

1、首先我们去掉限制 假设 能够取 无数次 也就是说一开始把他当做完全背包来考虑
离线DP 预处理 复杂度 4*v

用f[ i ] 表示 空间 为 i 的方案数
答案ans 其实就是所有方案 - 所有超过限制的方案 限制指的就是题目中限制 某个硬币有几枚

然后所有超过限制的方案用容斥来做

所有超过限制的方案 要减 == -1 超过限制的方案 - 2 超过限制的方案 - 3 超过限制的方案 - 4 超过限制的方案
+ 1和2 超过限制的方案 +1和3超过限制的方案 + 1和4 超过限制的方案 ..... - 1和2和3超过限制的方案 .....
+ 1和2和3和4 超过限制的方案
然后这样容斥就行了 因为只有四个数 相当于只要运算 16 次 就行 计算一次的复杂度为 O(1)

总的时间复杂度 4*maxv + T*(1) 复杂度 O(v+T)

----------------------------------


另外 来自 hzwer 的题解


我想起了cf的某道题。。。
dp预处理+容斥原理
byvoid:
设F[i]为不考虑每种硬币的数量限制的情况下,得到面值i的方案数。则状态转移方程为

F[i]=Sum{F[i-C[k]] | i-C[k]>=0 且 k=1..4}

为避免方案重复,要以k为阶段递推,边界条件为F[0]=1,这样预处理的时间复杂度就是O(S)。

接下来对于每次询问,奇妙的解法如下:根据容斥原理,答案为 得到面值S的超过限制的方案数 – 第1种硬币超过限制的方案数 – 第2种硬币超过限制的方案数 – 第3种硬币超过限制的方案数 – 第4种硬币超过限制的方案数 + 第1,2种硬币同时超过限制的方案数 + 第1,3种硬币同时超过限制的方案数 + …… + 第1,2,3,4种硬币全部同时超过限制的方案数。

当第1种硬币超过限制时,只要要用到D[1]+1枚硬币,剩余的硬币可以任意分配,所以方案数为 F[ S – (D[1]+1)C[1] ],当且仅当(S – (D[1]+1)C[1])>=0,否则方案数为0。其余情况类似,每次询问只用问16次,所以询问的时间复杂度为O(1)。

 1 #include <cstdio>
 2 #include <cmath>
 3 #include <cstdlib>
 4 #include <cstring>
 5 #include <string>
 6 #include <algorithm>
 7 #include <iomanip>
 8 #include <iostream> 
 9 #define ll long long 
10 using namespace std ; 
11 
12 int T,v ; 
13 int c[5],d[5]; 
14 ll ans ;
15 ll f[100011] ; 
16 
17 inline int read() 
18 {
19     char ch = getchar() ; 
20     int x = 0 , f = 1 ; 
21     while(ch<'0'||ch>'9') { if(ch=='-') f = -1 ; ch = getchar() ; } 
22     while(ch>='0'&&ch<='9') { x = x*10+ch-48 ; ch = getchar() ; }
23     return x*f ; 
24 }
25 
26 inline void dfs(int x,int k,ll sum) 
27 {
28     if(sum < 0 ) return ; 
29     if(x==5) 
30     {
31         if(k&1) 
32             ans-=f[sum] ;  
33         else 
34             ans+=f[sum] ; 
35         return ; 
36     }
37     dfs(x+1,k+1, sum-(d[x]+1)*c[x] ) ;       //    确保其  一定超出限制   保证  x  一定  超出 限制  
38     dfs(x+1,k,sum) ; 
39 }
40 
41 int main() 
42 {
43     for(int i=1;i<=4;i++) c[ i ] = read() ; 
44     T = read() ;
45     f[ 0 ] = 1 ;  
46     for(int i=1;i<=4;i++) 
47         for(int j=c[ i ];j<=100000;j++)     //  动归时 不要忘记边界   
48             f[ j ]+=f[ j-c[ i ] ] ; 
49     while(T--) 
50     {
51         for(int i=1;i<=4;i++) d[ i ] = read() ; 
52         v = read() ; 
53         ans = 0 ; 
54         dfs( 1,0,v ) ; 
55         printf("%lld
",ans) ;         
56     } 
57     return 0 ; 
58 }
原文地址:https://www.cnblogs.com/third2333/p/7065318.html