7-1 凑零钱
题意:给定一定的零钱,看能否凑数一个数字来。
题解:dfs+剪枝,本题只需要轻轻剪一下就够了,深搜的时候判断一下剩下的零钱总数够不够现在的总金额即可。
#include <iostream>
#include <algorithm>
#include <stack>
using namespace std;
stack<int>st;
int a[10005];
int n,m;
bool f(int num,int sum,int s) {//num当前时第几件物品,sum当前需要的剩下的总金额,s当前剩下的总金额
if(sum==0) {
return true;
}
if(num>=n||s<sum) {//剪枝,如果剩下的零钱数不够,则不需要继续了
return false;
}
s-=a[num];
if(sum>=a[num]) {//如果该零钱还可以放得下
sum-=a[num];
if(f(num+1,sum,s)) {
st.push(a[num]);
return true;
} else {
sum+=a[num];
}
}else{
return false;
}
if(f(num+1,sum,s)){//如果不放这件零钱
return true;
}
return false;
}
int main() {
scanf("%d%d",&n,&m);
int s=0;
for(int i=0; i<n; i++) {
scanf("%d",&a[i]);
s+=a[i];//计算总价值
}
sort(a,a+n);
if(f(0,m,s)) {
while(!st.empty()) {
cout<<st.top();
st.pop();
if(!st.empty()){
cout<<" ";
}
}
cout<<endl;
} else {
cout<<"No Solution"<<'\n';
}
}
7-2 拼题A打卡奖励
题意:给定一定的卷子,做每张卷子需要一定的时间,做完之后会给一定的金币,求在一定时间内获得的最大金币数。
题解:01背包,但是因为 时间的数量级很大,所以会超时,但是金币的数量级很小,所以可以调整01背包,改为总价值为x时,消耗的最小时间。最后遍历所有价值,看价值最大能达到多少,使得时间最小不超过m。
状态标识:f[i],总价值为i的最小时间。
初始化:因为求最小值,所以一开始初始化为正无穷,但是要注意,当金额为0时,消耗的时间也为0,所以f[0]=0。
状态转移:从前面枚举价值j,从大到小循环,最小到w[i]
f
[
j
]
=
m
i
n
(
f
[
j
]
,
f
[
j
−
w
[
i
]
]
+
v
[
i
]
)
f[j]=min(f[j],f[j-w[i]]+v[i])
f[j]=min(f[j],f[j−w[i]]+v[i])
#include <iostream>
#include <cstring>
using namespace std;
int main(){
int n,m;
cin>>n>>m;
int w[1005];
int v[1005];
const int mx=1e6+10;
int dp[mx];
memset(dp,99,sizeof dp);//因为求最小值,所以初始化为最大值
dp[0]=0;//金额为0,花费时间也为0
long long sum=0;
for(int i=1;i<=n;i++){
cin>>w[i];
}
for(int i=1;i<=n;i++){
cin>>v[i];
sum+=v[i];
}
for(int i=1;i<=n;i++){
for(long long j=sum;j>=v[i];j--){
dp[j]=min(dp[j],dp[j-v[i]]+w[i]);//求 在一定金额的前提下,花费的最小时间
}
}
for(long long j=sum;j>=0;j--){
if(dp[j]<=m){
cout<<j<<endl;
break;
}
}
}
7-3 球队“食物链”
题意:n支球队踢球赛,分主客场,每一场有输、赢、平三种结果,求一个环,前一个要赢过后一个
题解:dfs+剪枝,dfs所有情况,在dfs的时候需要注意,若剩下的球队都没有赢过第一支球队,则没必要深搜了,因为最后肯定要有一支球队赢过第一支球队。注意,如果该球队在客场赢过也算赢过
#include <bits/stdc++.h>
using namespace std;
class node {
public:
map<int,bool>mp;
int num;
};
int n;
stack<int>st;
node no[40];
bool dfs(int num,int ceng,bool fi[],int be){//当前是第num支球队,是环中的第几支球队,fi记录,已经有哪些球队被访问过来,be记录环是从哪里开始的
if(ceng==n){
if(no[num].mp.find(be)!=no[num].mp.end()){
st.push(num);
return true;
}else{
return false;
}
}
bool f=0;
for(int i=1;i<=n;i++){//查看剩下的球队有没有赢过初始球队的,用于剪枝
if(fi[i]==0&&no[i].mp.find(be)!=no[i].mp.end()){
f=1;
}
}
if(f==0){
return false;
}
map<int,bool>::iterator it;
for(it=no[num].mp.begin();it!=no[num].mp.end();it++){//深搜,该球队所英国的球队
int nx=it->first;
if(fi[nx]==1||no[num].mp[nx]!=1){//确保该球队没有被访问过
continue;
}
fi[nx]=1;
if(dfs(nx,ceng+1,fi,be)){
st.push(num);
return true;
}
fi[nx]=0;
}
return false;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
string str;
cin>>str;
no[i].num=i;
for(int j=0;j<n;j++){
if(str[j]=='W'){
no[i].mp[j+1]=1;//球队i赢过球队j+1
}
if(str[j]=='L'){
no[j+1].mp[i]=1;//球队j+1赢过球队i
}
}
}
bool f=0;
for(int i=1;i<=n;i++){
bool fi[40];
memset(fi,0,sizeof(fi));
fi[i]=1;
if(dfs(i,1,fi,i)){
f=1;
break;
}
}
if(!f){
cout<<"No Solution"<<'\n';
}else{
while(!st.empty()){
cout<<st.top();
st.pop();
if(!st.empty()){
cout<<" ";
}else{
cout<<endl;
break;
}
}
}
}
7-5 至多删三个字符
题意:给一个字符串,可以删0,1,2,3个字符,问最多能得到多少个不一样的字符
题解:动态规划+删重,每个字符都可以选择删或者不删,dp[i][j]表示前i个字符,删j个字符,能得到多少个字符串(里面有重复),若j=0,则dp[i][0]=dp[i-1][0],若j!=0,则dp[i][j]=dp[i-1][j]+dp[i-1][j-1],然后删重,看前面在当前j范围内,有没有和当前字符相同的,若有,则说明需要删重,dp[i][j]-=dp[k-1][j-(i-k)],这里k为与i相同的字符下标,j-(i-k)就是需要把两个字符之间的字符都删掉,这样,两个字符串就完全一样了。
#include<iostream>
#include<string>
#include<cstring>
#include<vector>
#include<set>
#include<cmath>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int mx=1000000+10;
ll a[mx][5];//必须为全局变量,不然会段错误,没理解
int main() {
string str;
cin>>str;
string s=" ";
str=s+str;
a[0][0]=1;
for(int i=1; i<str.size(); i++) {
for(int j=0; j<=3; j++) {
a[i][j]=a[i-1][j];
if(j!=0) {
a[i][j]+=a[i-1][j-1];//前i个字符删j个,结果取决于前i-1个字符删j-1个,或者前i-1个删j个
}
for(int k=i-1; k>=1&&i-k<=j; k--) {
if(str[i]==str[k]) {
a[i][j]-=a[k-1][j-(i-k)];//去重
break;//一次就好
}
}
}
}
const int t=str.size()-1;
cout<<a[t][0]+a[t][1]+a[t][2]+a[t][3]<<endl;
}