DP(正解完全背包+容斥)

DP

Time Limit:10000MS     Memory Limit:165888KB     64bit IO Format:%lld & %llu

Submit Status

Description

  硬币购物一共有4种硬币。面值分别为c1,c2,c3,c4。某人去商店买东西,去了tot次。每次带di枚ci硬币,买s
i的价值的东西。请问每次有多少种付款方法。

Input

  第一行 c1,c2,c3,c4,tot 下面tot行 d1,d2,d3,d4,s,其中di,s<=100000,tot<=1000

Output

  每次的方法数

Sample Input

1 2 5 10 2
3 2 3 1 10
1000 2 2 2 900

Sample Output

4
27

//背包问题,容斥原理//不得不说这是个好题,背包问题应该都会,主要是这个容斥原理,要理解,举个例子说明下日常中经常遇到的这个定理

一次考试,某班有15人数学得满分,有12人语文得满分,并且有4人语、数都是满分,那么这个班至少有一门得满分的同学有多少人?答案:15+12-4=23

思路是,首先不限制4种钱币的个数,看组成某个价格的方案数共有多少然后借助容斥定理减去4种钱币超过个数限制的情况,就不多不少的求出答案

dp[i]表示了|不限制| 硬币数目的最多付款方法,怎么转移应该都会
 那么只需将
 dp[res]
 -d1超过的限制数 - d2超过的... - d3... - d4...
 + (d1与d2) + ... + (d3与d4)
 - (d1,d2,d3)
 + (d1+d2+d3+d4)
 就行了

如果还不理解再继续看这题的运用容斥定理,看了篇博客,例子写的很好,不然我真不太好理解,尤其是 d[i]++ 是为什么我们来理解x=dp[s]-dp[s-(d1+1)*c1]的含义:x 表示 c1 硬币的数量不超过 d1 个而其他三种硬币的数量不限制拼成 s 的方案数。我们举着例子来说明,假设现在有两种硬币,面值分别为1和2,那么我们求出 dp:dp[0]=1,dp[1]=1,dp[2]=2,dp[3]=2,dp[4]=3,dp[5]=3,dp[6]=4。其中dp[3]的两种分别为3=1+1+1=1+2,dp[6]的四种为:6=1+1+1+1+1+1=1+1+1+1+2=1+1+2+2=2+2+2。加入我们现在求第一种硬币最多使用两个,第二种硬币无限制的方案数,按照我们说的 x = dp[6]-dp[6--(2+1)*1]=dp[6]-dp[3]=2。也就是6=1+1+2+2=2+2+2两种。我们发现我们删除了1+1+1+1+1+1和1+1+1+1+2两种,为什么能够通过减去dp[3]删掉这两种?我们来看dp[3],3=1+1+1=1+2,我们发现6中被删掉的两种正是通过这个f[3]增加3个1得到的。
先来易懂的用for容斥的代码

 1 #include <iostream>
 2 #include <stdio.h>
 3 using namespace std;
 4
 5 typedef long long LL;
 6 #define MAX 100005
 7
 8 LL c[5],d[5];
 9 LL dp[MAX];
10 LL ans;
11
12 int main()
13 {
14     for (int i=1;i<=4;i++)
15     scanf("%lld",&c[i]);
16     int tot;
17     scanf("%lld",&tot);
18     dp[0]=1;
19     for (int i=1;i<=4;i++)
20         for (int j=c[i];j<=100000;j++)
21             dp[j]+=dp[j-c[i]];
22     while (tot--)
23     {
24         LL res;
25         for (int i=1;i<=4;i++)
26         {
27             scanf("%lld",&d[i]);
28             d[i]++;
29         }
30
31         scanf("%lld",&res);
32         ans=dp[res];
33
34         int i,j,k;
35         for (i=1;i<=4;i++)
36             if (res>=c[i]*d[i])
37             ans-=dp[res-c[i]*d[i]];
38
39         for (i=1;i<=3;i++)
40             for (j=i+1;j<=4;j++)
41             if (res>=c[i]*d[i]+c[j]*d[j])
42             ans+=dp[res-c[i]*d[i]-c[j]*d[j]];
43         for (i=1;i<=2;i++)
44             for (j=i+1;j<=3;j++)
45                 for (k=j+1;k<=4;k++)
46                 if (res>=c[i]*d[i]+c[j]*d[j]+c[k]*d[k])
47                 ans-=dp[res-c[i]*d[i]-c[j]*d[j]-c[k]*d[k]];
48         if (res>=c[1]*d[1]+c[2]*d[2]+c[3]*d[3]+c[4]*d[4])
49             ans+=dp[res-c[1]*d[1]-c[2]*d[2]-c[3]*d[3]-c[4]*d[4]];
50
51         printf("%lld\n",ans);
52
53     }
54     return 0;
55 }


这个是DFS去处理容斥,简洁明了,时间是一样的

 1 #include <iostream>
 2 #include <stdio.h>
 3 using namespace std;
 4
 5 typedef long long LL;
 6 #define MAX 100005
 7
 8 LL c[5],d[5];
 9 LL dp[MAX];
10 LL ans;
11
12 void dfs(int x,int k,LL s)
13 {
14     if (s<0)return;
15     if (x==5)
16     {
17         if (k%2)ans-=dp[s];
18         else ans+=dp[s];
19         return;
20     }
21     dfs(x+1,k+1,s-(d[x]+1)*c[x]);
22     dfs(x+1,k,s);
23 }
24 int main()
25 {
26     for (int i=1;i<=4;i++)
27     scanf("%lld",&c[i]);
28     int tot;
29     scanf("%lld",&tot);
30     dp[0]=1;
31     for (int i=1;i<=4;i++)
32         for (int j=c[i];j<=100000;j++)
33             dp[j]+=dp[j-c[i]];
34
35     while (tot--)
36     {
37         LL res;
38         for (int i=1;i<=4;i++)
39             scanf("%lld",&d[i]);
40
41         scanf("%lld",&res);
42         ans= 0;
43         dfs(1,0,res);
44         printf("%lld\n",ans);
45     }
46     return 0;
47 }



时间: 2024-11-09 04:10:52

DP(正解完全背包+容斥)的相关文章

P1450 [HAOI2008]硬币购物(完全背包+容斥)

P1450 [HAOI2008]硬币购物 暴力做法:每次询问跑一遍多重背包. 考虑正解 其实每次跑多重背包都有一部分是被重复算的,浪费了大量时间 考虑先做一遍完全背包 算出$f[i]$表示买价值$i$东西的方案数 蓝后对每次询问价值$t$,减去不合法的方案 $c_1$超额方案$f[t-c_1*(d_1+1)]$,表示取了$d_1+1$个$c_1$,剩下随便取的方案数(这就是差分数组) 如法炮制,减去$c_2,c_3,c_4$的超额方案数 但是我们发现,我们多减了$(c_1,c_2),(c_1,c

「总结」容斥。二.反演原理

二.反演原理 0.综述 说一下个人对反演的理解. 反演是一种手段,一种处理已知信息和未知信息关系的手段,用来得到未知信息的方式.也就是以一种既定的手段在较小的时间复杂度内用已知的信息得到未知的信息. 还有$zsq$学长更加浅显的解读. 反演一般就是把一个好看但难算的式子转化成一个难看且难算的式子在转化为一个难看但好算的式子. 先来一个裸一点的反演 下面要说我知道的四种反演. 子集反演,针对的是集合交并的容斥. 二项式反演,针对组合原理的容斥. 莫比乌斯反演,针对约数和倍数的容斥. 斯特林反演,针

【容斥+大数】SGU 476 Coach&#39;s Trouble

通道 题意:有3*n个人,分成n组,每组三个人.给出k个三元组,这三个人不可组队,问最后可以组队的总方案数 思路: 当k=0时,有(C[3*n][3]*C[3*n-3][3]*……*C[3][3])/n!种方案,展开以后可以得到dp[n]=(3*n)!/n!/6^n. 显然可以写成递推式:dp[n]=dp[n-1]*(3*n-1)*(3*n-2)/2. 那么容斥一下,答案=总方案数-至少含一个禁止组合的+至少含两个禁止组合的-…… 二进制暴力TLE了.DFS的话会有很多剪枝,当前几个已经出现冲突

[Luogu P1450] [HAOI2008]硬币购物 背包DP+容斥

题面 传送门:https://www.luogu.org/problemnew/show/P1450 Solution 这是一道很有意思的在背包里面做容斥的题目. 首先,我们可以很轻松地想到暴力做背包的做法. 就是对于每一次询问,我们都做一次背包. 复杂度O(tot*s*log(di)) (使用二进制背包优化) 显然会T得起飞. 接下来,我们可以换一种角度来思考这个问题. 首先,我们可以假设没有每个物品的数量的限制,那么这样就会变成一个很简单的完全背包问题. 至于完全背包怎么写,我们在这里就不做

poj1015 正解--二维DP(完全背包)

题目链接:http://poj.org/problem?id=1015 错误解法: 网上很多解法是错误的,用dp[i][j]表示选择i个人差值为j的最优解,用path[i][j]存储路径,循环次序为"选的第几个人->选哪个人->差值之和"或者"选的第几个人->差值之和->选哪个人",为了避免选择重复的人需要判断.错误的原因是存储路径的方式使得会覆盖一些情况,比如1 3 5和2 4 6均满足dp[3][k]最优时,若采用2 4 6作为dp[3]

[Luogu#4707] 重返现世(min-max容斥+背包dp)

Address Luogu#4707 Solution 前置技能:记 \(max_k(S)\) 表示 \(S\) 中第 \(k\) 大的数,\(min(S)\) 表示 \(S\) 中最小的数,那么有:\[max_k(S)=\sum_{T∈S,T\neq\emptyset}\binom{|T|-1}{k-1}(-1)^{|T|-k}min(T)\]证明:我们考虑构造容斥系数,即设: \[max_k(S)=\sum_{T∈S,T\neq\emptyset}f(|T|)min(T)\]当 \(min(

「JSOI2019」神经网络(容斥+组合计数+背包dp)

Address luogu5333 loj3102 Solution 容易发现,一条哈密顿回路本质上就是:把每棵树都拆成若干条有向路径,再把所有的有向路径连接成环,环上的相邻两条有向路径不可以来自同一棵树. 先求出 \(g_{i,j}\) 表示把第 \(i\) 棵树拆成 \(j\) 条有向路径的方案数. 考虑 \(\text{dp}\),记 \(f_{u,i,0/1/2/3}\) 分别表示:\(u\) 的子树拆成 \(i\) 条路径,\(u\) 是路径起点,是路径终点,单点成路径,既不是路径起点

$bzoj2560$ 串珠子 容斥+$dp$

正解:容斥+$dp$ 解题报告: 传送门$QwQ$ $umm$虽然题目蛮简练的了但还是有点难理解,,,我再抽象一点儿,就说有$n$个点,点$i$和点$j$之间有$a_{i,j}$条无向边可以连,问有多少种方案可以连成一张联通图 显然考虑容斥呗?设$f_i$表示状态为$i$的点连成联通图的合法方案,$g_i$表示状态为$i$的点随便连边的所有方案 显然$g_i$可以先预处理出来?就等于$\prod_{u,v\in i}a_{u,v}$.然后$f_i$就等于$g_i$减去不合法的数量.不合法数量显然

Codechef MGCHGYM Misha and Gym 容斥、背包、Splay

VJ传送门 简化题意:给定一个长度为\(N\)的数列,\(Q\)个操作: \(1\,x\,a\).将数列中第\(x\)个元素改为\(a\) \(2\,l\,r\).反转子序列\([l,r]\) \(3\,l\,r\,w\).询问区间\([l,r]\)中是否存在若干个数和为\(w\),一个数只能取一次 注意:在整个过程中,在数列中出现过的数的种数不会超过\(K(K \leq 10)\). 注意到最后一个条件很奇怪-- 考虑询问实际上是:最开始给出不超过\(10\)个数\(a_1,...,a_{10