[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<bits/stdc++.h>
#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<N;++j)
            dp[j]+=dp[j-c[i]];
    T=read();
    while(T--){
        for(int i=1;i<=4;++i)d[i]=read();
        sum=read(),res=0;
        for(int i=0;i<=15;++i){
            int t=sum,cnt=0;
            for(int j=1;i<=4;++i)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;
}

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 <bits/stdc++.h>
#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;
}
posted @ 2022-05-03 18:58  _Famiglistimo  阅读(36)  评论(0编辑  收藏  举报