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

题面

传送门:https://www.luogu.org/problemnew/show/P1450


Solution

这是一道很有意思的在背包里面做容斥的题目。

首先,我们可以很轻松地想到暴力做背包的做法。

就是对于每一次询问,我们都做一次背包。

复杂度O(tot*s*log(di)) (使用二进制背包优化)

显然会T得起飞。

接下来,我们可以换一种角度来思考这个问题。

首先,我们可以假设没有每个物品的数量的限制,那么这样就会变成一个很简单的完全背包问题。

至于完全背包怎么写,我们在这里就不做过多讨论,如有需要,看看代码就能理解了。

完全背包做完后,我们可以得到一个f[i]表示填满i的背包的方案数的数组。

那么,我们接下来可以用容斥来解决不可行的方案的问题。

假设只有1件物品的使用次数超出了所给的数量,假设这件物品是第x件。

那么可以用  f[s-(d[x]+1)*c[x]] 表示这件物品不可行的方案总数。

因为对于花钱数为 s-(d[x]+1)*c[x] 里面的每一种方法,都可以通过使用购买 d[x]+1件的x物品来超出所给的数量。所以  f[s-(d[x]+1)*c[x]] 可以表示该物品不可行方案总数。

那么答案是四件物品不可行方案总数这和吗?

nope

因为我们会重复减去一些东西。例如:一种方案即超出了第一件物品的使用数,也超出了第二件物品的使用数,我们却重复扣除了这种方案两次

所以说我们这时候就得使用容斥来解决这个问题。

容斥中有一个很基础的定理(我不会证):

对于有n的限制条件的事件,只要其中符合一个条件就算可行,其可行方案总数为:

(符合其中0个(条件的方案数,后同)-符合其中1个+符合其中2个-符合其中3个+符合其中4个-符合其中5个+符合其中6个....)

那么,我们就可以根据这个定理求出不可行的方案总数。

对于这题来说,代码如下:

for(a[1]=0;a[1]<=1;a[1]++)
            for(a[2]=0;a[2]<=1;a[2]++)
                for(a[3]=0;a[3]<=1;a[3]++)
                    for(a[4]=0;a[4]<=1;a[4]++)
                    {
                        int cnt=0,t_s=s;
                        for(int j=1;j<=4;j++)
                            if(a[j]==1) cnt++,t_s-=(d[j]+1)*c[j];
                        if(cnt%2==0) cnt=1;
                        else cnt=-1;

                        if(t_s>=0)
                            ans=ans+cnt*f[t_s];
                    }

这样写绝对不推荐,因为这样写很丑。

那么,这题就可以AC啦


Code

//Luogu P1450 [HAOI2008]硬币购物
//Aug,27th,2018
//DP+容斥
#include <iostream>
#include <cstdio>
using namespace std;
long long read()
{
    long long x=0,f=1; char c=getchar();
    while(!isdigit(c)){if(c==‘-‘) f=-1;c=getchar();}
    while(isdigit(c)){x=x*10+c-‘0‘;c=getchar();}
    return x*f;
}
const int N=100000+1000;
long long f[N];
int n,m;
long long c[5],d[5];
int main(int argc, char **argv)
{
    for(int i=1;i<=4;i++)
        c[i]=read();
    m=read();

    f[0]=1;
    for(int j=1;j<=4;j++)
        for(int i=c[j];i<=100000;i++)
            f[i]+=f[i-c[j]];
    for(int i=1;i<=m;i++)
    {
        for(int j=1;j<=4;j++)
            d[j]=read();
        int s=read(),a[5];
        long long ans=0;
        for(a[1]=0;a[1]<=1;a[1]++)
            for(a[2]=0;a[2]<=1;a[2]++)
                for(a[3]=0;a[3]<=1;a[3]++)
                    for(a[4]=0;a[4]<=1;a[4]++)
                    {
                        int cnt=0,t_s=s;
                        for(int j=1;j<=4;j++)
                            if(a[j]==1) cnt++,t_s-=(d[j]+1)*c[j];
                        if(cnt%2==0) cnt=1;
                        else cnt=-1;

                        if(t_s>=0)
                            ans=ans+cnt*f[t_s];
                    }

        printf("%lld\n",ans);
    }
    return 0;
}

正解(c++)

原文地址:https://www.cnblogs.com/GoldenPotato/p/9545340.html

时间: 2024-11-05 00:19:19

[Luogu P1450] [HAOI2008]硬币购物 背包DP+容斥的相关文章

[BZOJ 1042] [HAOI2008] 硬币购物 【DP + 容斥】

题目链接:BZOJ - 1042 题目分析 首先 Orz Hzwer ,代码题解都是看的他的 blog. 这道题首先使用DP预处理,先求出,在不考虑每种硬币个数的限制的情况下,每个钱数有多少种拼凑方案. 为了避免重复的方案被转移,所以我们以硬币种类为第一层循环,这样阶段性的增加硬币. 一定要注意这个第一层循环要是硬币种类,并且初始 f[0] = 1. f[0] = 1; for (int i = 1; i <= 4; ++i) { for (int j = B[i]; j <= MaxS; +

BZOJ-1042: [HAOI2008]硬币购物 (背包DP+容斥原理)

1042: [HAOI2008]硬币购物 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 2888  Solved: 1777[Submit][Status][Discuss] Description 硬币购物一共有4种硬币.面值分别为c1,c2,c3,c4.某人去商店买东西,去了tot次.每次带di枚ci硬币,买si的价值的东西.请问每次有多少种付款方法. Input 第一行 c1,c2,c3,c4,tot 下面tot行 d1,d2,d3,d4,

【bzoj1042】[HAOI2008]硬币购物 背包dp+容斥原理

题目描述 硬币购物一共有4种硬币.面值分别为c1,c2,c3,c4.某人去商店买东西,去了tot次.每次带di枚ci硬币,买si的价值的东西.请问每次有多少种付款方法. 输入 第一行 c1,c2,c3,c4,tot 下面tot行 d1,d2,d3,d4,s,其中di,s<=100000,tot<=1000 输出 每次的方法数 样例输入 1 2 5 10 2 3 2 3 1 10 1000 2 2 2 900 样例输出 4 27 题解 背包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

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

洛谷P1450 [HAOI2008]硬币购物 动态规划 + 容斥原理 1.首先我们去掉限制 假设 能够取 无数次 也就是说一开始把他当做完全背包来考虑 离线DP 预处理 复杂度 4*v 用f[ i ] 表示 空间 为 i 的方案数 答案ans 其实就是所有方案 - 所有超过限制的方案 限制指的就是题目中限制 某个硬币有几枚 然后所有超过限制的方案用容斥来做 所有超过限制的方案 要减 == -1 超过限制的方案 - 2 超过限制的方案 - 3 超过限制的方案 - 4 超过限制的方案 + 1和2 超

洛谷P1450 [HAOI2008]硬币购物

题目描述 硬币购物一共有4种硬币.面值分别为c1,c2,c3,c4.某人去商店买东西,去了tot次.每次带di枚ci硬币,买si的价值的东西.请问每次有多少种付款方法. 输入输出格式 输入格式: 第一行 c1,c2,c3,c4,tot 下面tot行 d1,d2,d3,d4,s 输出格式: 每次的方法数 输入输出样例 输入样例#1: 1 2 5 10 2 3 2 3 1 10 1000 2 2 2 900 输出样例#1: 4 27 说明 di,s<=100000 tot<=1000 [HAOI2

@@ P1450 [HAOI2008]硬币购物

题目描述 硬币购物一共有4种硬币.面值分别为c1,c2,c3,c4.某人去商店买东西,去了tot次.每次带di枚ci硬币,买si的价值的东西.请问每次有多少种付款方法. 输入输出格式 输入格式: 第一行 c1,c2,c3,c4,tot 下面tot行 d1,d2,d3,d4,s 输出格式: 每次的方法数 输入输出样例 输入样例#1: 复制 1 2 5 10 2 3 2 3 1 10 1000 2 2 2 900 输出样例#1: 复制 4 27 说明 di,s<=100000 tot<=1000

bzoj1042硬币购物——递推+容斥

题目:http://www.lydsy.com/JudgeOnline/problem.php?id=1042 递推,再用容斥原理减掉多余的,加上多减的--(dfs)即可. 代码如下: #include<iostream> #include<cstdio> #include<cstring> using namespace std; typedef long long ll; ll c[5],tot,d[5],s,f[100005]; void dfs(ll x,ll

【BZOJ1042】[HAOI2008]硬币购物 容斥

[BZOJ10492][HAOI2008]硬币购物 Description 硬币购物一共有4种硬币.面值分别为c1,c2,c3,c4.某人去商店买东西,去了tot次.每次带di枚ci硬币,买si的价值的东西.请问每次有多少种付款方法. 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