1 条题解
-
0
自动搬运
来自洛谷,原作者为

I_AM_HelloWord
**搬运于
2025-08-24 21:25:13,当前版本为作者最后更新于2017-09-09 09:52:11,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
好巧妙的一道dp题!
如果我们就赤裸裸的多重背包那么就是O(10^5*10^5*1000)
一周的时间都运行不完(手动滑稽)!
那么怎么办呢?如果没有硬币数量的限制那就多好啊?直接一个完全背包预处理,然后O(1)输出就好了
可是有了硬币的限制怎么办?我们先考虑一个简单一点的情况:只有第一个硬币有限制。
如果我们用类似前缀和的思想(术语叫差分),我们先完全背包预处理好无限制的情况,拿dp[tot]减去dp[tot-c[i]*(d[i]+1)]就是我们所需的方案数。
这是为什么呢?为什么要弄个c[i]*(d[i]+1)?其实我们可以这样想,无限制的情况就是没有那个di,而有限制时,不应该计入答案的方案数就是把c[i]这个硬币取了超过d[i]次,对吧?那么我们手动先取出d[i]+1个c[i]的硬币,然后剩下的价值弄个完全背包,这时就是我们所不需要的答案, 把它减掉就行了。
那么对于4个(或更多)的硬币有限制,我们就逐一把4个硬币单独限制的方案数减掉,这时可能会减重了(即同时两个硬币有限制的情况减了两次),所以我们再把4个硬币两两同时限制的方案数加上,可能又加重了,再把4个硬币33同时限制减掉,最后加上4个同时限制的方案数就是我们所需的答案。这就是大名鼎鼎的容斥原理啊!写成位运算就很优美了!
方案数会爆int哟。
参考代码(和楼下不太一样,0表示满足限制,1表示不满足限制):
#include<cstdio> #include<algorithm> #include<cstring> #define REP(i,a,b) for (int i=(a);i<=(b);i++) #define in(a) scanf("%d",&(a)) using namespace std; const int N=100001; long long dp[N+10]; int c[5],d[5]; int main(){ REP(i,1,4)in(c[i]); dp[0]=1; REP(i,1,4)REP(j,c[i],N)dp[j]+=dp[j-c[i]]; int T;in(T); while (T--){ int sum;long long res=0; REP(i,1,4)in(d[i]); in(sum); REP(i,0,15){ long long t=sum; int cnt=0; REP(j,1,4)if ((i>>(j-1))&1)t-=c[j]*(d[j]+1),cnt^=1; if (t<0)continue; if (!cnt)res+=dp[t];else res-=dp[t]; } printf("%lld\n",res); } return 0; }
- 1
信息
- ID
- 444
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 5
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者