[HAOI2008]硬币购物 题解
[HAOI2008]硬币购物 题解
背包+容斥
Statement
[HAOI2008]硬币购物 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
Solution
根本没有往容斥方向思考 /kk
条件计数的路子无非两条,要么寻找更简洁的充要条件,要么容斥
——command_block
所以首先考虑找条件,发现题目是在数有多少 \(a\) ,满足 \(\sum_{i=1}^4 a_ic_i=s\)
这个条件好像不是很转化的动,所以我们考虑容斥
先一遍完全背包求出没有限制时 \(dp[n]\) 表示凑出 \(n\) 的方案数
假装现在只有一种硬币有限制,其他没有限制,限制使用 \(d\) 个,想算凑出 \(s\) 的方案数
合法的不好计算,我们容斥一下,计算不合法的情况,也就是使用了 \(\ge d+1\) 个此硬币的情况
容易想到不合法的情况数就是 \(dp[s-(d+1)\times c]\) ,也就是先把这 \(d+1\) 个选出来,剩下的在做完全背包的时候,不管选择了几个 \(c\) ,综合起来看肯定 \(\ge d+1\) 个。
即,在只有一个限制下,答案为 \(dp[s]-dp[s-(d+1)\times c]\)
多个限制再容斥一下就可以了
复杂度 \(O(s+16n)\)
Code
#include
#define int long long
using namespace std;
const int N = 1e5+5;
char buf[1<<23],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read(){
int s=0,w=1; char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))s=s*10+(ch^48),ch=getchar();
return s*w;
}
int dp[N],c[5],d[5];
int T,sum,res;
signed main(){
for(int i=1;i<=4;++i)c[i]=read();
dp[0]=1;
for(int i=1;i<=4;++i)
for(int j=c[i];j>(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;
}
Solution 2
伟大的 ttd 在深刻洞悉了多重背包的本质之后,卡住 \(O(1e8)\) 过题!
一般的多重背包是 \(O(nmk)\) 的,即物品种类数,容量,物品个数
for i=1...n for j=1..m for k=1...d
f[k+v_i*k]+=f[k]
容易发现这样做的时候,\(f[k]\) 贡献的位置是多个点,点与点之间距离固定
所以不妨按按照 \(v_i\) 的余数分组
\[f[u+p\times v]=\sum _{p-d_i\le k\le p-1} (f[u+k\times v]+p-k) \]固定外层 \(i\) ,枚举余数 \(u\) ,发现这个是一个滑动窗口
这样的话,对于每一个 \(i\) 而言,刚好会把所有 \(f\) 扫一遍,所以单次询问复杂度 \(O(s)\)
总复杂度 \(O(ns)\) ,刚好卡过
这个其实也就是所谓单调队列优化多重背包的思路
//copied from ttd!!1
#include
#define int long long
using namespace std;
const int N = 1e5 + 10;
inline int read()
{
int s = 0, w = 1;
char ch = getchar();
while(ch < '0' || ch > '9') { if(ch == '-') w *= -1; ch = getchar(); }
while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
return s * w;
}
int n, s;
int c[5], d[5], dp[5][N];
signed main()
{
c[1] = read(), c[2] = read(), c[3] = read(), c[4] = read(), n = read();
while(n--){
d[1] = read(), d[2] = read(), d[3] = read(), d[4] = read(), s = read();
memset(dp, 0, sizeof(dp)), dp[0][0] = 1;
for(register int i = 1; i <= 4; i++){
for(register int j = 0; j < c[i]; j++){
int sum = 0;
for(register int k = 0; j + k * c[i] <= s; k++){
sum += dp[i - 1][j + k * c[i]];
if(k - d[i] - 1 >= 0) sum -= dp[i - 1][j + (k - d[i] - 1) * c[i]];
dp[i][j + k * c[i]] += sum;
}
}
}
printf("%lld\n", dp[4][s]);
}
return 0;
}