[HAOI2008]硬币购物 题解

[HAOI2008]硬币购物 题解

背包+容斥

Statement

[HAOI2008]硬币购物 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

Solution

根本没有往容斥方向思考 /kk

条件计数的路子无非两条,要么寻找更简洁的充要条件,要么容斥

——command_block

所以首先考虑找条件,发现题目是在数有多少 a ,满足 i=14aici=s

这个条件好像不是很转化的动,所以我们考虑容斥

先一遍完全背包求出没有限制时 dp[n] 表示凑出 n 的方案数

假装现在只有一种硬币有限制,其他没有限制,限制使用 d 个,想算凑出 s 的方案数

合法的不好计算,我们容斥一下,计算不合法的情况,也就是使用了 d+1 个此硬币的情况

容易想到不合法的情况数就是 dp[s(d+1)×c] ,也就是先把这 d+1 个选出来,剩下的在做完全背包的时候,不管选择了几个 c ,综合起来看肯定 d+1 个。

即,在只有一个限制下,答案为 dp[s]dp[s(d+1)×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] 贡献的位置是多个点,点与点之间距离固定

所以不妨按按照 vi 的余数分组

f[u+p×v]=pdikp1(f[u+k×v]+pk)

固定外层 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 @   _Famiglistimo  阅读(38)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示