采药问题
01背包模板略。
货币系统
可以发现b一定是a的子集,那么对a做一个完全背包,能被其它货币表示的货币就不在新的系统里。
但是,如果这种就错了。
完全背包
for(int i=1;i<=n;++i) if(f[a[i]]>1) m--;
因为这样之前就已经判定可以不要的货币依旧对后面产生了贡献,组成这个可以不要的货币的那些货币也对后面产生了重复贡献,导致错误。
当前货币只能被若干面值比它小的货币代替,所以要先排序。
#include<bits/stdc++.h>
#define ll long long
#define ff(i,s,e) for(int i(s);i<=(e);++i)
using namespace std;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=105,M=1e5+5;
int n,a[N],f[M];
inline void solve(){
n=read();
int maxx=0;
ff(i,1,n) a[i]=read(),maxx=max(maxx,a[i]);
memset(f,0,sizeof(f));
f[0]=1;
sort(a+1,a+n+1);
int m=n;
ff(i,1,n){
if(f[a[i]]){--m;continue;}
ff(j,a[i],maxx) f[j]+=f[j-a[i]];
}
printf("%d\n",m);
}
signed main(){
int T=read();
while(T--) solve();
return 0;
}
宝物筛选
多重背包模板略。
硬币方案
多重背包模板++。
金明的预算方案
有依赖的01背包模板。
只考虑每一个主件,因为它的附件最多只有两个,所以最多只有五种情况:
- 不选主件
- 只选主件
- 选主件+附件1
- 选主件+附件2
- 选主件+附件1+附件2
对这些情况分别进行转移,就变成了01背包,注意数组不要越界。
#include<bits/stdc++.h>
#define ll long long
#define ff(i,s,e) for(int i(s);i<=(e);++i)
using namespace std;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=32005,M=65;
int lim,n;
int v[M],w[M];
int sv[M][3],sw[M][3],cnt[M];
int f[N];
signed main(){
lim=read(),n=read();
ff(i,1,n){
int ww=read(),vv=read(),q=read();
if(!q) v[i]=vv*ww,w[i]=ww;
else ++cnt[q],sv[q][cnt[q]]=vv*ww,sw[q][cnt[q]]=ww;
}
ff(i,1,n){
if(!v[i]) continue;
for(int j=lim;j>=w[i];--j){
f[j]=max(f[j],f[j-w[i]]+v[i]);
if(cnt[i]>=1) if(j>=w[i]+sw[i][1]) f[j]=max(f[j],f[j-w[i]-sw[i][1]]+v[i]+sv[i][1]);
if(cnt[i]>=2){
int sumw=w[i]+sw[i][2],sumv=v[i]+sv[i][2];
if(j>=sumw) f[j]=max(f[j],f[j-sumw]+sumv);
sumw+=sw[i][1],sumv+=sv[i][1];
if(j>=sumw) f[j]=max(f[j],f[j-sumw]+sumv);
}
}
}
printf("%d",f[lim]);
return 0;
}
求好感度
多重背包模板++。
购买商品
先对A做01背包求最大价值,再遍历找最少需要多少钱能买到最大价值,用剩余的钱对B做完全背包即可。
魔法开锁
把每个盒子看成点,向它的钥匙能打开的盒子连有向边,构成一个图。由于每个盒子只有一把钥匙,所以这个图一定由若干环组成,那么只需要在每个环里都选至少一个点就能打开所有盒子。
设 f [ i ] [ j ] f[i][j] f[i][j] 表示前 i i i 个环里选 j j j 个点打开的方案数,则有转移式: f [ i ] [ j ] = ∑ k = 0 j − 1 f[i][j]=\sum_{k=0}^{j-1} f[i][j]=∑k=0j−1 f [ i − 1 ] [ k ] ∗ ( s i z [ i ] j − k ) f[i-1][k]*\dbinom{siz[i]}{j-k} f[i−1][k]∗(j−ksiz[i]),其中 s i z [ i ] siz[i] siz[i] 表示第 i i i 个环的大小,那么只需要预处理一下组合数就能 O ( n 3 ) O(n^3) O(n3) 求解了。
组合数会爆long long,但是不会超double,所以可以用double来存组合数,避免写高精。
递推求组合数不要忘了初始化 c [ 0 ] [ 0 ] = 1 c[0][0]=1 c[0][0]=1 啊/kk
#include<bits/stdc++.h>
#define ll long long
#define ff(i,s,e) for(int i(s);i<=(e);++i)
using namespace std;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=305;
int n,t,a[N];
int siz[N],tot;
bool vis[N];
double c[N][N],f[N][N];
inline void solve(){
memset(vis,0,sizeof(vis));
memset(f,0,sizeof(f));
memset(siz,0,sizeof(siz));
tot=0;
n=read(),t=read();
ff(i,1,n) a[i]=read();
ff(i,1,n){
if(vis[i]) continue;
siz[++tot]=1,vis[i]=1;
int u=i;
while(1){
u=a[u];
vis[u]=1;
if(u==i) break;
++siz[tot];
}
}
f[0][0]=1;
ff(i,1,tot) ff(j,0,t) ff(k,0,j-1) f[i][j]+=f[i-1][k]*c[siz[i]][j-k];
printf("%.9lf\n",f[tot][t]/c[n][t]);
}
signed main(){
c[0][0]=1;
ff(i,1,300) ff(j,0,i){
if(!j) c[i][j]=1;
else c[i][j]=c[i-1][j]+c[i-1][j-1];
}
int T=read();
while(T--) solve();
return 0;
}
购买礼物
设 f [ i ] [ j ] [ k ] [ 0 ] f[i][j][k][0] f[i][j][k][0] 为选前 i i i 个物品,第一张卡花 j j j 元,第二张卡花 k k k 元,没免费拿礼物时取得的最大价值, f [ i ] [ j ] [ k ] [ 1 ] f[i][j][k][1] f[i][j][k][1] 为已经免费拿完礼物时取得的最大价值。那么枚举 i , j , k i,j,k i,j,k,做01背包即可。重点在初始化要考虑必须买的限制并算上本次免费的情况:
- s [ i ] = 1 s[i]=1 s[i]=1,初始不免费拿的价值为 − i n f -inf −inf,使以后转移用不到这一选择,初始免费拿的价值为 f [ i − 1 ] [ j ] [ k ] [ 0 ] + v [ i ] f[i-1][j][k][0]+v[i] f[i−1][j][k][0]+v[i];
- s [ i ] = 0 s[i]=0 s[i]=0,初始不免费拿的价值为 f [ i − 1 ] [ j ] [ k ] [ 0 ] f[i-1][j][k][0] f[i−1][j][k][0],免费拿的价值为 max ( f [ i − 1 ] [ j ] [ k ] [ 1 ] , f [ i − 1 ] [ j ] [ k ] [ 0 ] + v [ i ] ) \max(f[i-1][j][k][1],f[i-1][j][k][0]+v[i]) max(f[i−1][j][k][1],f[i−1][j][k][0]+v[i])。
然后就比较简单了qwq。
#include<bits/stdc++.h>
#define ll long long
#define ff(i,s,e) for(int i(s);i<=(e);++i)
using namespace std;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=305,M=505,K=55;
int v1,v2,n;
int w[N],v[N],s[N];
int f[N][M][K][2];
signed main(){
v1=read(),v2=read(),n=read();
ff(i,1,n) w[i]=read(),v[i]=read(),s[i]=read();
ff(i,1,n) ff(j,0,v1) ff(k,0,v2){
if(s[i]) f[i][j][k][0]=-1e9,f[i][j][k][1]=max((int)-1e9,f[i-1][j][k][0]+v[i]);
else f[i][j][k][0]=f[i-1][j][k][0],f[i][j][k][1]=max(f[i-1][j][k][1],f[i-1][j][k][0]+v[i]);
if(j>=w[i]){
f[i][j][k][0]=max(f[i][j][k][0],f[i-1][j-w[i]][k][0]+v[i]);
f[i][j][k][1]=max(f[i][j][k][1],f[i-1][j-w[i]][k][1]+v[i]);
}
if(k>=w[i]){
f[i][j][k][0]=max(f[i][j][k][0],f[i-1][j][k-w[i]][0]+v[i]);
f[i][j][k][1]=max(f[i][j][k][1],f[i-1][j][k-w[i]][1]+v[i]);
}
}
int ans=max(f[n][v1][v2][0],f[n][v1][v2][1]);
if(ans>0) printf("%d",ans);
else printf("-1");
return 0;
}
课题选择
令 f [ i ] [ j ] f[i][j] f[i][j] 为在前 i i i 种课题里选 j j j 个完成所用的最小时间,观察到数据范围很小,可以枚举 i i i 和 j j j,再枚举第 i i i 个课题的选择次数,然后就是一个朴素的背包问题了,时间复杂度 O ( n 2 m ) O(n^2m) O(n2m)。
因为忘了算不选这个课题的情况WA了一发qwq
#include<bits/stdc++.h>
#define int long long
#define ff(i,s,e) for(int i(s);i<=(e);++i)
using namespace std;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=205,M=25;
int n,m,a[M],b[M];
int f[N][N];
inline int ksm(int x,int y){
int res=1;
for(;y;y>>=1){
if(y&1) res=res*x;
x=x*x;
}
return res;
}
signed main(){
n=read(),m=read();
ff(i,1,m) a[i]=read(),b[i]=read();
ff(i,1,n) f[0][i]=1e17;//初始化
ff(i,1,m){
ff(j,1,n){
f[i][j]=f[i-1][j];//初始化为不选的情况
ff(k,1,j)
f[i][j]=min(f[i][j],f[i-1][j-k]+a[i]*ksm(k,b[i]));
}
}
printf("%lld",f[m][n]);
return 0;
}