背包问题
01背包问题
有N件物品和一个容量为V的背包。放入第i件物品耗费的费用是ci,得到的价值是wi。求解将哪些物品装入背包可使价值总和最大。
集合求和
题目描述
对于从1到N (1<=N<=39) 的连续整数集合,能划分成两个子集合,且保证每个集合的数字和是相等的。
举个例子,如果N=3,对于{1,2,3}能划分成两个子集合,他们每个的所有数字和是相等的:
{3} 和 {1,2}
这是唯一一种分法(交换集合位置被认为是同一种划分方案,因此不会增加划分方案总数)
如果N=7,有四种方法能划分集合{1,2,3,4,5,6,7},每一种分法的子集合各数字和是相等的:
{1,6,7} 和 {2,3,4,5} {注 1+6+7=2+3+4+5} {2,5,7} 和 {1,3,4,61,3,4,6} {3,4,73,4,7} 和 {1,2,5,6} {1,2,4,7} 和 {3,5,6}
给出N,你的程序应该输出划分方案总数,如果不存在这样的划分方案,则输出00。程序不能预存结果直接输出
这个题目也是0/1背包,就是关于第i个数取或不取的问题。
初始状态:
d
p
[
1
]
[
0
]
=
1
dp[1][0]=1
dp[1][0]=1;
d
p
[
1
]
[
1
]
=
1
dp[1][1]=1
dp[1][1]=1;
状态方程:
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
+
d
p
[
i
−
1
]
[
j
−
i
]
(
j
>
=
i
)
dp[i][j]=dp[i−1][j]+dp[i−1][j−i](j>=i)
dp[i][j]=dp[i−1][j]+dp[i−1][j−i](j>=i);
第一个部分不取,第二个部分取
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
(
i
>
j
)
dp[i][j]=dp[i−1][j](i>j)
dp[i][j]=dp[i−1][j](i>j);
因为算了两次,所以答案除以2
#include<bits/stdc++.h>
using namespace std;
long long f[9001][9001];
long long n;
int main()
{
freopen("input.in","r",stdin);
freopen("output.out","w",stdout);
cin>>n;
long long sum=n*(n+1)/2;
if(sum%2)
{
cout<<0<<endl;
return 0;
}
else
{
long long v=sum/2;
f[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=sum;j++)
f[i][j]=f[i-1][j]+f[i-1][j-i];
cout<<f[n][v];
}
return 0;
}
采药
题目描述
宁智贤是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。
医师为了判断他的资质,给他出了一个难题。
医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。
如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是宁智贤,你能完成这个任务吗?
输入格式
输入的第一行有两个整数T(1<=T<=1000)和M(1<=M<=100),用一个空格隔开,T代表总共能够用来采药的时间,代表山洞里的草药的数目。
接下来的M行每行包括两个在1到100之间(包括1和100)的整数,分别表示采摘某株草药的时间和这株草药的价值。
输出格式
输出包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。
样例数据
input
70 3
71 100
69 1
1 2
output
3
不难写出状态转移方程
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
]
[
j
]
,
f
[
i
−
1
]
[
j
−
v
[
i
]
]
+
w
[
i
]
)
f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i])
f[i][j]=max(f[i][j],f[i−1][j−v[i]]+w[i])
但是会爆空间,所以可以用滚动数组来写
当然可以把二维数组优化到一维数组,这里我们介绍一个模板
我们通过观察二维数组的状态表示,发现
f
[
i
]
[
c
]
f[i][c]
f[i][c]只与
f
[
i
−
1
]
f[i−1]
f[i−1]这一层有关系,所以我们考虑将i这个维度给优化掉。进一步发现
f
[
i
]
[
c
]
f[i][c]
f[i][c]只与
f
[
i
−
1
]
[
c
]
f[i−1][c]
f[i−1][c]和
f
[
i
−
1
]
[
c
−
w
[
i
]
]
f[i−1][c−w[i]]
f[i−1][c−w[i]]有关,如果想把i这个维度省掉,第i层的
f
[
c
]
f[c]
f[c]只会与第i−1层的
f
[
c
]
f[c]
f[c]和
f
[
c
−
w
[
i
]
f[c−w[i]
f[c−w[i]有关,因为
f
[
c
−
w
[
i
]
]
f[c-w[i]]
f[c−w[i]]在
f
[
c
]
f[c]
f[c]的左边,所以
for(int i=1;i<=n;i++)
for(int c=C;c>=0;c--)
if(c>=w[i]) f[c]=max(f[c],f[c-w[i]+v[i]);
那么这样看来,这个题是不是很(秒)妙 了呢
对了还要注意恰好的时候要让f[0]=0,其它为-∞;
#include<bits/stdc++.h>
using namespace std;
int t,m;
int p[1100],v[1100];
int f[1100];
int main()
{
freopen("input.in","r",stdin);
freopen("output.out","w",stdout);
cin>>t>>m;
for(int i=1;i<=m;i++) cin>>p[i]>>v[i];
memset(f,0,sizeof(f));
f[0]=0;
for(int i=1;i<=m;i++)
for(int j=t;j>=1;j--)
if(j>=p[i])
f[j]=max(f[j],f[j-p[i]]+v[i]);
cout<<f[t];
}
最大约数和
题目描述
选取和不超过S的若干个不同的正整数,使得所有数的约数(不含它本身)之和最大。
输入输出格式
输入格式
输入一个正整数S。
输出格式
输出最大的约数之和。
输入输出样例
输入样例 #1
11
输出样例 #1
9
求因子和就先把所范围里的因子先求出来,作为价值(预处理)
让sum[1]=0 往后推就可以了
状态转移方程
f
[
j
]
=
m
a
x
(
f
[
j
]
,
f
[
j
−
i
]
+
s
u
m
[
i
]
f[j]=max(f[j],f[j-i]+sum[i]
f[j]=max(f[j],f[j−i]+sum[i];
#include<bits/stdc++.h>
using namespace std;
int sum[1100];
int m;
int f[1100];
void ss()
{
for(int j=2;j<=1010;j++)
{
for(int i=1;i<=sqrt(1.0*j);i++)
{
if(i==sqrt(1.0*j))
{
sum[j]+=i;
break;
}
if(i==1) sum[j]-=j;
if(j%i==0)
{
sum[j]+=i;
sum[j]+=j/i;
}
}
}
}
int main()
{
freopen("divsum.in","r",stdin);
freopen("divsum.out","w",stdout);
cin>>m;
ss();
sum[1]=0;
for(int i=1;i<=m;i++)
for(int j=m;j>=0;j--)
if(j>=i)
f[j]=max(f[j],f[j-i]+sum[i]);
cout<<f[m];
return 0;
}