HJ1 字符串最后一个单词的长度
描述
计算字符串最后一个单词的长度,单词以空格隔开,字符串长度小于5000。(注:字符串末尾不以空格为结尾)
输入描述:
输入一行,代表要计算的字符串,非空,长度小于5000。
输出描述:
输出一个整数,表示输入字符串最后一个单词的长度。
方法一
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s;
getline(cin, s);
int ans=0, i=s.length()-1;
while(i>=0 && s[i]!=' ')
{
i--;
ans++;
}
cout<<ans;
return 0;
}
方法二
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s;
while (cin>>s);
cout<<s.size();
}
HJ2 计算某字符出现次数(字符串,哈希)
描述
写出一个程序,接受一个由字母、数字和空格组成的字符串,和一个字符,然后输出输入字符串中该字符的出现次数。(不区分大小写字母)
数据范围: 1≤n≤1000
输入描述:
第一行输入一个由字母和数字以及空格组成的字符串,第二行输入一个字符。
输出描述:
输出输入字符串中含有该字符的个数。(不区分大小写字母)
方法一:哈希表
#include<iostream>
#include<unordered_map>
using namespace std;
int main(){
char target;
unordered_map<char, int> mp;
char c;
while((c = getchar()) != '\n'){ //按字符输入字符串
if(c >= 'A' && c <= 'Z') //大写转小写
c = c - 'A' + 'a';
mp[c]++; //统计频率
}
cin >> target; //输入目标字符
if(target >= 'A' && target <= 'Z') //大写转小写
target = target - 'A' + 'a';
cout << mp[target] << endl;
return 0;
}
方法二:遍历统计法
#include<iostream>
#include<vector>
#include<string>
using namespace std;
int main(){
vector<string> s;
string input;
while(cin >> input) //将字符串以空格分割作为单词保存在数组
s.push_back(input);
char target = s[s.size() - 1][0]; //第二行输入的目标字符也会被上方代码读取,数组最后一个字符串就是目标字符
if(target >= 'A' && target <= 'Z') //大写转小写
target = target - 'A' + 'a';
int res = 0;
for(int i = 0; i < s.size() - 1; i++){ //遍历数组的每个字符
for(int j = 0; j < s[i].length(); j++){
char c = s[i][j];
if(c >= 'A' && c <= 'Z') //大写转小写
c = c - 'A' + 'a';
if(c == target) //记录出现次数
res++;
}
}
cout << res << endl;
return 0;
}
HJ3 明明的随机数(数组)
描述
明明生成了NN个1到500之间的随机整数。请你删去其中重复的数字,即相同的数字只保留一个,把其余相同的数去掉,然后再把这些数从小到大排序,按照排好的顺序输出。
数据范围:1≤n≤1000 ,输入的数字大小满足 1≤val≤500
输入描述:
第一行先输入随机整数的个数 N 。 接下来的 N 行每行输入一个整数,代表明明生成的随机数。 具体格式可以参考下面的"示例"。
输出描述:
输出多行,表示输入数据处理后的结果
方法一:暴力排序去重
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main(){
int n;
while(cin >> n){ //首先输入每次调查的人数n
vector<int> v(n);
for(int i = 0 ; i < n; i++) //连续输入n个整数
cin >> v[i];
sort(v.begin(), v.end()); //排序
for(int i = 0; i < n; i++){ //去重输出
if(i != 0 && v[i] == v[i - 1])
continue;
else
cout << v[i] << endl;
}
}
return 0;
}
方法二:有序集合
#include<iostream>
#include<set>
#include<algorithm>
using namespace std;
int main(){
int n;
set<int> s;
while(cin >> n){ //首先输入每次调查的人数n
s.clear(); //每次调查清空集合
for(int i = 0 ; i < n; i++){
int temp;
cin >> temp; //连续输入n个整数
s.insert(temp); //插入集合中,自动排序去重
}
for(auto iter = s.begin(); iter != s.end(); iter++) //遍历集合直接输出即可
cout << *iter << endl;
}
return 0;
}
HJ4 字符串分隔
描述
•输入一个字符串,请按长度为8拆分每个输入字符串并进行输出;
•长度不是8整数倍的字符串请在后面补数字0,空字符串不处理。
输入描述:
连续输入字符串(每个字符串长度小于等于100)
输出描述:
依次输出所有分割后的长度为8的新字符串
#include <string>
#include <vector>
#include <iostream>
using namespace std;
int main() {
string s;
getline(cin, s);
while (true) {
int len = s.size();
if (len <= 8) {
s.insert(s.end(), 8-len, '0');
cout << s << endl;
if (!getline(cin, s)) break;
}
else {
cout << s.substr(0, 8) << endl;
s = s.substr(8, len-8);
}
}
return 0;
}
HJ5 进制转换
描述
写出一个程序,接受一个十六进制的数,输出该数值的十进制表示。
数据范围:保证结果在1≤n≤2^31−1
输入描述:
输入一个十六进制的数值字符串。
输出描述:
输出该数值的十进制字符串。不同组的测试用例用\n隔开。
方法一:遍历转换
#include<iostream>
#include<string>
#include<cmath>
using namespace std;
int main(){
string s;
while(cin >> s){ //连续读取字符串
int bit = 0; //记录当前位数
int res = 0;
for(int i = s.length() - 1; i > 1; i--){
if(s[i] >= '0' && s[i] <= '9'){
res += (s[i] - '0') * pow(16, bit); //当前数字乘16的位数次方
bit++;
}
else if(s[i] >= 'A' && s[i] <= 'F'){
res += (s[i] - 'A' + 10) * pow(16, bit); //字母要转化成数字
bit++;
}
}
cout << res << endl;
}
return 0;
}
方法二:流输入输出的格式化
#include<iostream>
using namespace std;
int main(){
int res = 0;
while(cin >> hex >> res) //hex表示读入十六进制数
cout << dec << res << endl; //dec表示输出十进制数
return 0;
}
HJ6 质数因子
描述
功能:输入一个正整数,按照从小到大的顺序输出它的所有质因子(重复的也要列举)(如180的质因子为2 2 3 3 5 )
数据范围: 1≤n≤2×10^9+14
输入描述:
输入一个整数
输出描述:
按照从小到大的顺序输出它的所有质数的因子,以空格隔开。
方法一:迭代
#include<iostream>
#include<cmath>
using namespace std;
int main(){
long n;
cin >> n;
for(long i = 2; i <= sqrt(n) && i <= n; i++){ //从小到大的质因子,质因子不会超过它的开方
while(n % i == 0){ //所有的质数前面全部除掉,后续就不会有合因子
cout << i << " ";
n /= i; //除掉质因子
}
}
if(n - 1) //自己本身就是质数
cout << n << " ";
return 0;
}
方法二:递归
#include<iostream>
#include<cmath>
using namespace std;
void recursion(long n){//递归函数
for(long i = 2; i <= sqrt(n); i++){ //每次遍历到n的开方就行了
if(n % i == 0){
cout << i << " ";
recursion(n / i); //递归解决后续更小的
return;
}
}
if(n - 1 > 0) //自己就是质数
cout << n << " ";
}
int main(){
long n;
cin >> n;
recursion(n);
return 0;
}
HJ7 取近似值
描述
写出一个程序,接受一个正浮点数值,输出该数值的近似整数值。如果小数点后数值大于等于 0.5 ,向上取整;小于 0.5 ,则向下取整。
数据范围:保证输入的数字在 32 位浮点数范围内
输入描述:
输入一个正浮点数值
输出描述:
输出该数值的近似整数值
方法一:比较判断法
#include<iostream>
#include<cmath>
using namespace std;
int main(){
float x;
cin >> x;
int y = x / 1; //得到整数部分
if(x - (float)y < 0.5) //判断小数部分与0.5的大小
cout << y << endl; //四舍
else
cout << y + 1 << endl; //五入
return 0;
}
方法二:强制类型转换
#include<iostream>
using namespace std;
int main(){
float x;
cin >> x;
cout << (int)(x+0.5) << endl; //强制类型转换
return 0;
}
HJ8 合并表记录
描述
数据表记录包含表索引index和数值value(int范围的正整数),请对表索引相同的记录进行合并,即将相同索引的数值进行求和运算,输出按照index值升序进行输出。
提示:
0 <= index <= 11111111
1 <= value <= 100000
输入描述:
先输入键值对的个数n(1 <= n <= 500)
接下来n行每行输入成对的index和value值,以空格隔开
输出描述:
输出合并后的键值对(多行)
方法一:桶排序(空间溢出)
#include <iostream>
#define size 11111112 // 申请的空间大小
using namespace std;
int main() {
int n;
cin >> n;
int kv[size] = {0}; // 初始化空间
while(n) {
int k, v;
cin >> k;
cin >> v;
kv[k] += v; // 对每一个对应的桶进行value值的装填
n--;
}
for(int i = 0; i < size; i++) {
if(kv[i]) cout << i << " " << kv[i] << endl; // 根据桶排序的索引顺序输出键值对
}
return 0;
}
方法二:红黑树
#include <iostream>
#include <map>
using namespace std;
int main() {
int n;
cin >> n;
map<int, int> kv; // 声明map数据结构
while(n) {
int k, v;
cin >> k;
cin >> v; // 装填key-value信息
kv[k] += v; // 根据key值在value上累加
n--;
}
for(auto [k, v] : kv) {
cout << k << " " << v << endl; // 按照map中元素的顺序进行访问
}
}
方法三
#include <iostream>
#include <algorithm>
using namespace std;
//合并表记录的函数接口
int ConsolidateTableRecords (int num) {
int index; //索引,key(键)
int value; //数值,值
int a[1000] = {0}; //初始化一个数组,用于记录输入的合并表
int b[1000] = {0}; //*初始化一个数组,用于记录出现的索引
int max = 0; //记录输入的最大索引值
while (num--) {
//将数组的下标视为索引,下标对应的元素值视为数值
cin >> index >> value;
//相同索引对应的数值自动合并
a[index] += value;
b[index] = 1; //*补充当索引值对应的数值累加和为零的情况
if (index >= max) {
max = index;
}
}
//按key值升序输出,直到输出最大索引值 index 对应的数值 value 为止
for(int i = 0; i <= max; i++) {
//*增加了 if 语句的第二个判定条件
if ((a[i] > 0) || ((a[i] == 0) && (b[i] == 1))) {
cout << i << ' ' << a[i] << endl;
}
}
return 0;
}
//主函数
int main () {
int num;
while (cin >> num) {
ConsolidateTableRecords (num);
}
return 0;
}
HJ9 提取不重复的整数
描述
输入一个 int 型整数,按照从右向左的阅读顺序,返回一个不含重复数字的新的整数。
保证输入的整数最后一位不是 0 。
数据范围: 1≤n≤10^28
输入描述:
输入一个int型整数
输出描述:
按照从右向左的阅读顺序,返回一个不含重复数字的新的整数
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int main() {
int num;
cin>>num; //输入一个int型整数
vector<int> vec; //使用vector容器
while(num) {
if(find(vec.begin(), vec.end(), num%10) == vec.end()) //插入数据前判断是否已有该数字
vec.push_back(num%10); //从右向左添加数字
num /= 10; //除以10
}
int n=0; //初始化一个新的整数
for(int i=0;i<vec.size();i++) {
n *= 10;
n += vec[i];
}
cout<<n<<endl;
}
HJ10 字符个数统计
描述
编写一个函数,计算字符串中含有的不同字符的个数。字符在 ASCII 码范围内( 0~127 ,包括 0 和 127 ),换行表示结束符,不算在字符里。不在范围内的不作统计。多个相同的字符只计算一次
例如,对于字符串 abaca 而言,有 a、b、c 三种不同的字符,因此输出 3 。
数据范围: 1≤n≤500
输入描述:
输入一行没有空格的字符串。
输出描述:
输出 输入字符串 中范围在(0~127,包括0和127)字符的种数。
方法一:使用数组记录每种字符是否已经出现 (哈希表思想)
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int ans=0;
int vis[128]={0};
string str;
int main() {
cin>>str;
int len=str.length();
for(int i=0;i<len;i++){
int asc=(int)str[i];
if(vis[asc]==0) { //如果当前字符没出现过
vis[asc]=1; //标记该字符的出现
ans++; //增加字符种类统计数
}
}
cout<<ans<<endl;
return 0;
}
方法二:使用集合来统计(同样是哈希表的思想)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <set>
using namespace std;
string str;
set<char> ascs;
int main() {
cin>>str;
int len=str.length();
for(int i=0;i<len;i++){
ascs.insert(str[i]); //向集合中添加字符
}
cout<<ascs.size()<<endl; //输出集合的尺寸
return 0;
}
HJ11 数字颠倒
描述
输入一个整数,将这个整数以字符串的形式逆序输出
程序不考虑负数的情况,若数字含有0,则逆序形式也含有0,如输入为100,则输出为001
数据范围: 0≤n≤2^30−1
输入描述:
输入一个int整数
输出描述:
将这个整数以字符串的形式逆序输出
方法一:转换为字符串
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main() {
string s;
cin >> s; // 以字符串格式输入
reverse(s.begin(), s.end()); // reverse来倒序原有的字符串
cout << s;
return 0;
}
方法二:数学方法转换
#include<iostream>
using namespace std;
int main()
{
int n;
cin >> n;
if(n == 0) cout<<0;
while(n) {
cout << n % 10; // 取个位数字并输出
n /= 10; // 整除10
}
return 0;
}
HJ12 字符串反转
描述
接受一个只包含小写字母的字符串,然后输出该字符串反转后的字符串。(字符串长度不超过1000)
输入描述:
输入一行,为一个只包含小写字母的字符串。
输出描述:
输出该字符串反转后的字符串。
方法一:逆序拼接
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
int main(){
string s;
cin >> s;
string output = ""; //从一个空串开始
for(int i = s.length() - 1; i >= 0; i--) //逆序遍历字符串
output += s[i]; //将字符加到新串后面
cout << output << endl;
return 0;
}
方法二:双指针交换
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
int main(){
string s;
cin >> s;
//左右双指针
int left = 0;
int right = s.length() - 1;
while(left < right){ //两指针往中间靠
swap(s[left], s[right]); //交换两边字符
left++;
right--;
}
cout << s << endl;
return 0;
}
方法三:反转函数
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
int main(){
string s;
cin >> s;
reverse(s.begin(), s.end()); //逆转函数
cout << s;
return 0;
}
HJ13 句子逆序
描述
将一个英文语句以单词为单位逆序排放。例如“I am a boy”,逆序排放后为“boy a am I”
所有单词之间用一个空格隔开,语句中除了英文字母外,不再包含其他字符
数据范围:输入的字符串长度满足1≤n≤1000
注意本题有多组输入
输入描述:
输入一个英文语句,每个单词用空格隔开。保证输入只包含空格和字母。
输出描述:
得到逆序的句子
方法一
#include<iostream>
using namespace std;
int main() {
string s,str="";
while(cin>>s) {//循环输入流 ctrl+z表示最后输入结束
s+= " " +str;
str=s;
}
cout <<str<< endl;
return 0;
}
方法二:两次反转
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
int main(){
string s, temp;
while(cin >> temp) //输入字符串
s += " " + temp;
int n = s.length();
reverse(s.begin(), s.end()); //第一次整体反转
for(int i = 0; i < n; i++){
int j = i;
while(j < n && s[j] != ' ') //以空格为界找到一个单词
j++;
reverse(s.begin() + i, s.begin() + j); //将这个单词反转
i = j;
}
cout << s << endl;
return 0;
}
方法二:分割字符串+栈
#include<iostream>
#include<string>
#include<algorithm>
#include<stack>
using namespace std;
int main(){
string s, temp;
while(cin >> temp) //输入字符串
s += " " + temp;
int n = s.length();
stack<string> st;
for(int i = 0; i < n; i++){ //遍历字符串,找到单词并入栈
int j = i;
while(j < n && s[j] != ' ') //以空格为界,分割单词
j++;
st.push(s.substr(i, j - i)); //单词进栈
i = j;
}
s = "";
while(!st.empty()){ //栈遵循先进后厨,单词顺序是反的
s += st.top();
st.pop();
if(!st.empty())
s += " ";
}
cout << s << endl;
return 0;
}
方法三:输入时反向拼接
#include<iostream>
#include<string>
#include<algorithm>
#include<stack>
using namespace std;
int main(){
string s, temp;
while(cin >> temp){ //输入字符串
temp += " " + s; //每个单词加在字符串前面
s = temp;
}
cout << s << endl;
return 0;
}
HJ14 字符串排序
描述
给定 n 个字符串,请对 n 个字符串按照字典序排列。
数据范围: 1≤n≤1000 ,字符串长度满足 1≤len≤100
输入描述:
输入第一行为一个正整数n(1≤n≤1000),下面n行为n个字符串(字符串长度≤100),字符串中只含有大小写字母。
输出描述:
数据输出n行,输出结果为按照字典序排列的字符串。
方法一:冒泡排序
#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
#include<queue>
using namespace std;
int main(){
int n;
cin >> n;
vector<string> strs; //字符串数组
string s;
for(int i = 0; i < n; i++){ //输入n个字符串
cin >> s;
strs.push_back(s); //堆排序
}
for(int i = 0; i < n; i++)
for(int j = 1; j < n; j++)
if(strs[j] < strs[j - 1]){//比较并冒泡
string temp = strs[j]; //交换
strs[j] = strs[j - 1];
strs[j - 1] = temp;
}
for(int i = 0; i < n; i++) //输出
cout << strs[i] << endl;
return 0;
}
方法二:堆排序
#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
#include<queue>
using namespace std;
int main(){
int n;
cin >> n;
priority_queue<string, vector<string>, greater<string> > strs; //小根堆
string s;
for(int i = 0; i < n; i++){ //输入n个字符串
cin >> s;
strs.push(s); //堆排序
}
while(!strs.empty()){ //从小到大输出
cout << strs.top() << endl;
strs.pop();
}
return 0;
}
方法三:库函数快排
#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
using namespace std;
int main(){
int n;
cin >> n;
vector<string> strs;
string s;
for(int i = 0; i < n; i++){ //输入n个字符串
cin >> s;
strs.push_back(s);
}
sort(strs.begin(), strs.end()); //排序函数
for(int i = 0 ;i < n; i++) //输出
cout << strs[i] << endl;
return 0;
}
方法四:有序集合
#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
#include<set>
using namespace std;
int main(){
int n;
cin >> n;
multiset<string> strs; //可重复的有序集合
string s;
for(int i = 0; i < n; i++){ //输入n个字符串
cin >> s;
strs.insert(s); //加入集合中
}
for(auto iter = strs.begin(); iter != strs.end(); iter++) //遍历集合输出
cout << *iter << endl;
return 0;
}
方法五:归并排序
#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
using namespace std;
vector<string> temp;
void mergeSort(vector<string>& strs, int l, int r) {
if (l >= r)
return;
int mid = (l + r) / 2; //中间划分
mergeSort(strs, l, mid); //排序两边
mergeSort(strs, mid + 1, r);
int i = l, j = mid + 1;
int cnt = 0;
//合并
while (i <= mid && j <= r) { //依次比较,先取较小值
if (strs[i] <= strs[j])
temp[cnt++] = strs[i++];
else
temp[cnt++] = strs[j++];
}
while (i <= mid) //剩余的元素
temp[cnt++] = strs[i++];
while (j <= r)
temp[cnt++] = strs[j++];
for (int i = 0; i < r - l + 1; ++i)
strs[i + l] = temp[i];
}
int main(){
int n;
cin >> n;
vector<string> strs; //可重复的有序集合
string s;
for(int i = 0; i < n; i++){ //输入n个字符串
cin >> s;
strs.push_back(s); //加入集合中
}
temp.resize(n);
mergeSort(strs, 0, n - 1); //归并排序
for(auto iter = strs.begin(); iter != strs.end(); iter++) //遍历排序后的结果输出
cout << *iter << endl;
return 0;
}
HJ15 求int型正整数在内存中存储时1的个数
描述
输入一个 int 型的正整数,计算出该 int 型数据在内存中存储时 1 的个数。
数据范围:保证在 32 位整型数字范围内
输入描述:
输入一个整数(int类型)
输出描述:
这个数转换成2进制后,输出1的个数
方法一:转化二进制
#include<iostream>
#include<string>
using namespace std;
int main(){
int n;
cin >> n;
int count = 0;
string s = "";
while(n){ //十进制转化成二进制
s += ((n % 2) + '0'); //用字符串记录二进制每一位
n /= 2;
}
for(int i = 0; i < s.length(); i++) //遍历字符串统计1的个数
if(s[i] == '1')
count++;
cout<< count << endl;
return 0;
}
方法二:移位运算
#include<iostream>
using namespace std;
int main(){
int n;
cin >> n;
int count = 0;
while(n){
if(n & 1) //和最后一位按位与运算
count++; //与的结果是1说明这位是1
n >>= 1; //移位
}
cout<< count << endl;
return 0;
}
方法三:用位与去掉二进制末尾1
#include<iostream>
using namespace std;
int main(){
int n;
cin >> n;
int count = 0;
while(n){
count++; //统计+1
n &= (n - 1); //去掉末尾的1
}
cout<< count << endl;
return 0;
}
HJ16 购物单
描述
王强决定把年终奖用于购物,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
主件 | 附件 |
电脑 | 打印机,扫描仪 |
书柜 | 图书 |
书桌 | 台灯,文具 |
工作椅 | 无 |
如果要买归类为附件的物品,必须先买该附件所属的主件,且每件物品只能购买一次。
每个主件可以有 0 个、 1 个或 2 个附件。附件不再有从属于自己的附件。
王强查到了每件物品的价格(都是 10 元的整数倍),而他只有 N 元的预算。除此之外,他给每件物品规定了一个重要度,用整数 1 ~ 5 表示。他希望在花费不超过 N 元的前提下,使自己的满意度达到最大。
满意度是指所购买的每件物品的价格与重要度的乘积的总和,假设设第ii件物品的价格为v[i]v[i],重要度为w[i]w[i],共选中了kk件物品,编号依次为,则满意度为:(其中 * 为乘号)
请你帮助王强计算可获得的最大的满意度。
输入描述:
输入的第 1 行,为两个正整数N,m,用一个空格隔开:
(其中 N ( N<32000 )表示总钱数, m (m <60 )为可购买的物品的个数。)
从第 2 行到第 m+1 行,第 j 行给出了编号为 j-1 的物品的基本数据,每行有 3 个非负整数 v p q
(其中 v 表示该物品的价格( v<10000 ), p 表示该物品的重要度( 1 ~ 5 ), q 表示该物品是主件还是附件。如果 q=0 ,表示该物品为主件,如果 q>0 ,表示该物品为附件, q 是所属主件的编号)
输出描述:
输出一个正整数,为张强可以获得的最大的满意度。
方法一:动态规划
- 实现思路
- 我们规定
dp[i][j]
表示在 [ 前i
] 个物品里面 [ 预算值(背包)容量允许为j
] 的情况下可以获得的最大的价值加权和 - 对于01背包问题,我们的动态规划决策是当前物品是否要选择。
dp[i][j]=max(dp[i−1][j],dp[i−1][j−w[j]]+v[j])dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[j]] + v[j])dp[i][j]=max(dp[i−1][j],dp[i−1][j−w[j]]+v[j])
- 但是由于本题有主附件的选择考虑,因此我们将选择的情况划分为更多种类
- 不选择当前物品
- 选择【当前主件物品】
- 选择【当前主件 + 附件1】
- 选额【当前主件 + 附件2】
- 选择【当前主件 + 附件1 + 附件2】
- 因此有动态转移方程
dp[i][j]=max(dp[i−1][j],四种选择方案)dp[i][j] = max(dp[i-1][j], 四种选择方案)dp[i][j]=max(dp[i−1][j],四种选择方案)
-
同时本题处理上的一个关键内容在于如何访问我们的所有物品。依据我们的方案,我们需要将物品重新处理成新的数据结构,这种结构要求一定是主件优先被访问到,并且绑定主件和附件的关系
-
并且由于价格都是10的整数倍,我们统一在数据处理的时候都缩小十倍处理
- 我们规定
- 因此我们有如下的表格结构
假设现在的输入内容为
1 2 3 4 5 6 |
|
重新组织结构后(价格和加权价值全部是除以10后的结果)
索引 | 主件价格 | 主件加权价值 | 附件1价格 | 附件1加权价值 | 附件2价格 | 附件2加权价值 |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 1 | 3 | 0 | 0 | 0 | 0 |
4 | 1 | 2 | 0 | 0 | 0 | 0 |
5 | 1 | 1 | 2 | 6 | 2 | 6 |
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
int main() {
int N, m, v, p, q;;
cin >> N >> m;
N /= 10;
// 重新处理数据,整理成 "主件(价格+加权价值)+附件1(价格+加权价值)+附件2(价格+加权价值)" 的结构
vector<vector<int>> items(m + 1, vector<int>(6, 0));
for(int i = 1; i <= m; i++) {
cin >> v >> p >> q; // 价格 权重 主附
v /= 10;
p *= v;
if(q == 0) { // 如果当前是主件
items[i][0] = v;
items[i][1] = p;
} else if(items[q][2] == 0) { // 如果当前附件1位置为空
items[q][2] = v;
items[q][3] = p;
} else { // 只剩下附件2的位置
items[q][4] = v;
items[q][5] = p;
}
}
vector<vector<int>> dp(m + 1, vector<int>(N + 1, 0));
// dp[i][j] 表示在前i个里面预算值(背包)容量允许为j的情况下可以获得的最大的价值加权和
for(int i = 1; i <= m; i++) {
for(int j = 1; j <= N; j++) {
int a = items[i][0], d = items[i][1]; // 主件的价格+加权价值
int b = items[i][2], e = items[i][3]; // 附件1的价格+加权价值
int c = items[i][4], f = items[i][5]; // 附件2的价格+加权价值
dp[i][j] = dp[i-1][j];
if(j >= a) dp[i][j] = max(dp[i-1][j-a] + d, dp[i-1][j]); // 只挑选一个主件
if(j >= a+b) dp[i][j] = max(dp[i-1][j-a-b] + d+e, dp[i][j]); // 挑选主件+附件1
if(j >= a+c) dp[i][j] = max(dp[i-1][j-a-c] + d+f, dp[i][j]); // 挑选主件+附件2
if(j >= a+b+c) dp[i][j] = max(dp[i-1][j-a-b-c] + d+e+f, dp[i][j]); // 挑选主件+附件1+附件2
}
}
cout << dp[m][N] * 10 <<endl;
return 0;
}
方法二:空间记忆递归
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int recursion(int m, int N, vector<vector<int>>& price, vector<vector<int>>& multi_sum, vector<vector<int>>& dp){
if(m == 0 || N == 0)
return dp[m][N] = 0;
else if(price[m][0] > N){ //买不了该主件,进入下一个
if(dp[m - 1][N] == -1)
dp[m - 1][N] = recursion(m - 1, N, price, multi_sum, dp);
return dp[m - 1][N];
}
else{ //可以装下主件的情况
if(dp[m - 1][N] == -1)
dp[m - 1][N] = recursion(m - 1, N, price, multi_sum, dp); //不装
if(dp[m - 1][N - price[m][0]] == -1)
dp[m - 1][N - price[m][0]] = recursion(m - 1, N - price[m][0], price, multi_sum, dp); //装
dp[m][N] = max(dp[m - 1][N], dp[m - 1][N - price[m][0]] + multi_sum[m][0]); //对于第m项主件的情况
if(N >= price[m][0] + price[m][1]){//买得起主件+第一个附件
if(dp[m - 1][N - price[m][0] - price[m][1]] == -1)
dp[m - 1][N - price[m][0] - price[m][1]] = recursion(m - 1, N - price[m][0] - price[m][1], price, multi_sum, dp);
dp[m][N] = max(dp[m][N], dp[m - 1][N - price[m][0] - price[m][1]] + multi_sum[m][0] + multi_sum[m][1]);
}
if(N >= price[m][0] + price[m][2]){//买得起主件+第2个附件
if(dp[m - 1][N - price[m][0] - price[m][2]] == -1)
dp[m - 1][N - price[m][0] - price[m][2]] = recursion(m - 1, N - price[m][0] - price[m][2], price, multi_sum, dp);
dp[m][N] = max(dp[m][N], dp[m - 1][N - price[m][0] - price[m][2]] + multi_sum[m][0] + multi_sum[m][2]);
}
if(N >= price[m][0] + price[m][1] + price[m][2]){//买得起主件+两个附件
if(dp[m - 1][N - price[m][0] - price[m][1] - price[m][2]] == -1)
dp[m - 1][N - price[m][0] - price[m][1] - price[m][2]] = recursion(m - 1, N - price[m][0] - price[m][1] - price[m][2], price, multi_sum, dp);
dp[m][N] = max(dp[m][N], dp[m - 1][N - price[m][0] - price[m][1] - price[m][2]] + multi_sum[m][0] + multi_sum[m][1] + multi_sum[m][2]);
}
return dp[m][N];
}
return 0;
}
int main(){
int N, m;
cin >> N >> m;
N /= 10; //所有价格及金钱都是10倍数
vector<vector<int> > price(m + 1, vector<int>(3, 0)); //记录价格及附件价格
vector<vector<int> > multi_sum(m + 1, vector<int>(3, 0)); //记录重要度*价格
vector<vector<int> > dp(m + 1, vector<int>(N + 1, -1)); //dp数组
for(int i = 1; i <= m; i++){ //遍历输入,将所有价格及乘积信息录入数组
int v, p, q;
cin >> v >> p >> q; //输入价格、重要度、附属信息
v /= 10; //所有价格及金钱都是10倍数
p *= v; //重要度乘上价格
if(q == 0){ //主件
price[i][0] = v;
multi_sum[i][0] = p;
}else{ //附件
if(price[q][1] == 0){ //第一个附件
price[q][1] = v;
multi_sum[q][1] = p;
}else{ //第二个附件
price[q][2] = v;
multi_sum[q][2] = p;
}
}
}
cout << recursion(m, N, price, multi_sum, dp) * 10 << endl; //输出要乘回10倍
return 0;
}
方法三:动态规划空间优化
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int main(){
int N, m;
cin >> N >> m;
N /= 10; //所有价格及金钱都是10倍数
vector<vector<int> > price(m + 1, vector<int>(3, 0)); //记录价格及附件价格
vector<vector<int> > multi_sum(m + 1, vector<int>(3, 0)); //记录重要度*价格
vector<vector<int> > dp(2, vector<int>(N + 1, 0)); //dp数组
for(int i = 1; i <= m; i++){ //遍历输入,将所有价格及乘积信息录入数组
int v, p, q;
cin >> v >> p >> q; //输入价格、重要度、附属信息
v /= 10; //所有价格及金钱都是10倍数
p *= v; //重要度乘上价格
if(q == 0){ //主件
price[i][0] = v;
multi_sum[i][0] = p;
}else{ //附件
if(price[q][1] == 0){ //第一个附件
price[q][1] = v;
multi_sum[q][1] = p;
}else{ //第二个附件
price[q][2] = v;
multi_sum[q][2] = p;
}
}
}
int k = 0;
for(int i = 1; i <= m; i++){
k = 1 - k; //滚动数组
for(int j = 1; j <= N; j++){
if(j >= price[i][0]) //可以买这个的主件
dp[k][j] = max(dp[1 - k][j - price[i][0]] + multi_sum[i][0], dp[1 - k][j]); //选择买这个与不买这个的最大值
else
dp[k][j] = dp[1 - k][j];
if(j >= price[i][0] + price[i][1]) //可以买主件 + 第一个附件
dp[k][j] = max(dp[1 - k][j - price[i][0] - price[i][1]] + multi_sum[i][0] + multi_sum[i][1], dp[k][j]);
if(j >= price[i][0] + price[i][2]) //可以买主件 + 第二个附件
dp[k][j] = max(dp[1 - k][j - price[i][0] - price[i][2]] + multi_sum[i][0] + multi_sum[i][2], dp[k][j]);
if(j >= price[i][0] + price[i][1] + price[i][2]) //可以买主件 + 两个附件
dp[k][j] = max(dp[1 - k][j - price[i][0] - price[i][1] - price[i][2]] + multi_sum[i][0] + multi_sum[i][1] + multi_sum[i][2], dp[k][j]);
}
}
cout << dp[k][N] * 10 << endl; //输出要乘回10倍
return 0;
}