入门OJ:Coin

题目描述

你有n个硬币,第i硬币面值为ai,现在总队长想知道如果丢掉了某个硬币,剩下的硬币能组成多少种价值?(0价值不算)

输入格式

第一行一个整数n

第二行n个整数。,a1,a2…an。

1<=n<=100,1<=ai<=3000

输出格式

输出n行

第i行表示没有第i个硬币能组成多少种价值。


显然是个背包题,代价设计为硬币面值,价值设计为有几种方案组成当前面值。

那么设dp(i,j)表示前i个硬币有几种方案组成价值j,可以得出转移方程:

\[if(j>=val[i])dp[i][j]+=dp[i-1][j-val[i]]\\ else:dp[i][j]=dp[i-1][j] \]

压成一维之后:

\[dp[j]+=dp[j-val[i]];val[i]≤j≤m \]

那么我们发现每一位的j可以由若干个状态更新过来。根据加法交换律,某个编号为i的硬币更新j状态的dp(j-val(i))先加后加都无所谓。根据这一点,我们可以认为任意一个硬币是最后被加进去的。

结合我们的结论和题目,当我们丢掉了一个硬币之后就相当于撤销这个硬币对dp数组的贡献。我们可以认为它是最后被更新上去的,所以我们只需要把更新的操作反着执行一遍即可:

for(register int j=val[i];j<=m;j++) dp[j]-=dp[j-val[i]];

然后我们的答案是能凑出的价值的个数,只需要枚举求一下有那些价值可以被≥1种方案凑出即可。

时间复杂度为O(NM)。

* dp数组在计算中会爆longlong,记得开高精。或者int128

* c数组可以直接用一个变量sum代替

#include<iostream>
#include<cstring>
#include<cstdio>
#define maxn 101
#define maxm 300001
using namespace std;

__int128 dp[maxm];
int n,m,val[maxn],c[maxm];

inline int read(){
	register int x(0),f(1); register char c(getchar());
	while(c<'0'||'9'<c){ if(c=='-') f=-1; c=getchar(); }
	while('0'<=c&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return x*f;
}

inline void update(){
	for(register int i=1;i<=m;i++) c[i]=c[i-1]+(dp[i]>0);
}
inline void add(const int &val){
	for(register int i=m;i>=val;i--) dp[i]+=dp[i-val];
}
inline void del(const int &val){
	for(register int i=val;i<=m;i++) dp[i]-=dp[i-val];
}

int main(){
	n=read(),dp[0]=1;
	for(register int i=1;i<=n;i++) val[i]=read(),m+=val[i];
    for(register int i=1;i<=n;i++) add(val[i]);
    for(register int i=1;i<=n;i++){
        del(val[i]),update(),printf("%d\n",c[m]),add(val[i]);
    }
	return 0;
}
posted @ 2019-06-11 20:52  修电缆的建筑工  阅读(191)  评论(0编辑  收藏  举报