基本是DP的练习题,不会太难qwq
【T1】洛谷 P1164 小A点菜
- 有M(1<=M<=10000)元钱,有N(1<=N<=100)种菜,
- 第i种卖a[i]元(1<=a[i]<=10000),请你求出花完M元的点菜方法。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
/*【T1】洛谷 P1164 小A点菜
有M(1<=M<=10000)元钱,有N(1<=N<=100)种菜,
第i种卖a[i]元(1<=a[i]<=10000),请你求出花完M元的点菜方法。*/
/*【分析】设f[j]表示剩余j元时的最大方案数。
f[0] = 1 -> 没钱的时候什么都不买,也算是一种方案。
递归式:f[j] = f[j-cost[i]]。*/
int n,m,cost,f[1001];
int main(){
scanf("%d%d",&n,&m);
memset(f,0,sizeof(f)); //DP数组清零
f[0] = 1; //设置DP起点
for(int i=1;i<=n;i++){
scanf("%d",&cost);
for(int j=m;j>=cost;j--){
f[j]+=f[j-cost]; //DP方程
}
}
cout<<f[m]<<endl;
return 0;
}
【T2】caioj 1411 打牌
- 应该有n张牌,但少了几张,已知牌的总重量tw,
- 和每张牌的重量wi,把缺少的牌找出来。
- 如果无解,输出“0”,如果多解,输出“-1”。
- 否则按照升序输出缺少牌的编号(相邻两个数用空格隔开)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
/*【T2】caioj 1411 打牌
应该有n张牌,但少了几张,已知牌的总重量tw,
和每张牌的重量wi,把缺少的牌找出来。
如果无解,输出“0”,如果多解,输出“-1”。
否则按照升序输出缺少牌的编号(相邻两个数用空格隔开)。 */
/*【分析】f[j]表示前i张牌的形成重量j的方案数。
1.记录每张牌对方案数f[j]的改变。方案数的求法:f[j]+=f[j-a[i]];
f[j-a[i]]表示当前牌出现之前,形成重量j-a[i]的方案数。
f[0]=1,即重量为0的方案数初始化为1。
2.用c[j]记录重量j会用到的牌的序号
3.输出时根据f[w]的值判断,用v表示重量,从w倒推,
用ans[c[v]]表示第c[v]张牌是否出现,
因为之前在输入中用c[v]来表示重量v中一定会出现的牌的最大序号。
v-=a[c[v]]表示除去第c[v]张牌后的剩余重量,直到为0。
然后顺序判断ans[i],若为0,则表示第i张牌未出现,输出i即可。*/
int w,n,f[110002]={1}; //f数组初始化为1
int c[110002],ans[102],a[102];
int main(){
cin>>w>>n; //总重+牌数
for(int i=1;i<=n;i++){
cin>>a[i];
for(int j=w;j>=a[i];j--)
if(f[j-a[i]]){
f[j]+=f[j-a[i]];//不同重量带来的方案数
if(!c[j]) c[j]=i; //重量j会用到的牌的序号
//这里只用记录凑到这种情况的第一个序号
//因为减了它之后,余下的能提供其他的牌的序号
}
}
if(!f[w]) cout<<"0\n";
if(f[w]>1) cout<<"-1\n"; //方案数>1
if(f[w]==1){
int v=w; while(v) ans[c[v]]=1,v-=a[c[v]];
for(int i=1;i<=n;i++)
if(!ans[i]) cout<<i<<' ';
cout<<endl;
}
return 0;
}
【T3】洛谷 P1832 A+B problem
- 给定一个正整数n,求将其分解成若干个素数之和的方案总数。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
/*【T3】洛谷 P1832 A+B problem(再升级)
给定一个正整数n,求将其分解成若干个素数之和的方案总数。 */
/*【分析】无限背包dp。状态转移求方案数。
把1~n的所有素数看做是物品,每个素数可以无限放,注意f[0]=1;
F[j]+=F[j-a[i]],把素数a[i]放入背包后,增加方案数。
筛法求出素数,累加质数的状态之和。 */
bool mark[1005];
ll dp[1500],prime[1005],n,num;
int main(){
scanf("%d",&n);
for(int i=2;i<=n;i++){ //素数表
if(mark[i]==false) prime[num++]=i; //记录素数
for(int j=0;j<num&&prime[j]*i<=n;j++){
mark[prime[j]*i]=true; //↑↑线性筛法
if(i%prime[j]==0) break;
}
}
dp[0]=1;
for(int i=0;i<num;i++)
for(int j=prime[i];j<=n;j++)
dp[j]+=dp[j-prime[i]]; //方案数累加
printf("%lld",dp[n]);
return 0;
}
【T4】洛谷 P2285 打鼹鼠
- 在一个N*N(1<=n<=1000)的网格中,控制一个机器人打鼹鼠,
- 如果[i时刻]鼹鼠和机器人处于同一网格,就可以打死。
- 机器人每一时刻只能向上下左右四个网格移动一格,或原地不动。
- 游戏开始时,你可以自由选定机器人的初始位置。
- 现在你知道在一段时间内有M(1<=M<=10000)个鼹鼠和它出现的时间T和地点(X,Y),
- 要求你求出机器人最多打死的鼹鼠数量。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
/*【T4】洛谷 P2285 打鼹鼠
在一个N*N(1<=n<=1000)的网格中,控制一个机器人打鼹鼠,
如果[i时刻]鼹鼠和机器人处于同一网格,就可以打死。
机器人每一时刻只能向上下左右四个网格移动一格,或原地不动。
游戏开始时,你可以自由选定机器人的初始位置。
现在你知道在一段时间内有M(1<=M<=10000)个鼹鼠和它出现的时间T和地点(X,Y),
要求你求出机器人最多打死的鼹鼠数量。*/
/*【分析】不需要清晰地知道某一个时刻机器人的位置以及机器人的走法,
只用关心某个时间点,机器人能不能打死那个位置上的鼹鼠。
用f[i]表示打完前i个鼹鼠、并且打死第i个鼹鼠时,打死的最多数量。
那么对于之前的某个f[j],如果j鼹鼠和i鼹鼠的距离在足够时间单位内,
那么f[i]可以从f[j]转移过来。*/
const int N=10005;
int n,m,t[N],x[N],y[N],f[N],ans=0;
int dis(int i,int j){ return abs(x[i]-x[j])+abs(y[i]-y[j]); }
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d%d",&t[i],&x[i],&y[i]);
for(int i=1;i<=m;i++){
f[i]=1; //初始化:最初的时刻肯定可以打死
for(int j=1;j<i;j++)
if(dis(i,j)<=(t[i]-t[j]))
f[i]=max(f[i],f[j]+1);
ans=max(ans,f[i]);
}
printf("%d\n",ans);
return 0;
}
【T5】洛谷 P1006 传纸条
- 给出M*N(1<=M,N<=50)的矩阵,在这个矩阵内找出两条从1,1到m,n的路径。
- (一条从1,1到m,n;一条从m,n到1,1),且路径之上的权值和k最大。
- PS:(1,1)和(m,n)权值为0。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
/*【T5】洛谷 P1006 传纸条
给出M*N(1<=M,N<=50)的矩阵,在这个矩阵内找出两条从1,1到m,n的路径。
(一条从1,1到m,n;一条从m,n到1,1),且路径之上的权值和k最大。
PS:(1,1)和(m,n)权值为0。 */
const int maxn=60;
int a[maxn][maxn];
int F[2*maxn][maxn][maxn];
/* 第一维度维护的是纵坐标与横坐标的和。
=>在同一斜线上 =>结合纵坐标就可以找到确切位置。
第二维度维护的是相对在左边的点的纵坐标。
第三维度维护的是相对在右边的点的纵坐标。*/
//F[sum][i][j]=max{F[sum-1][i][j],F[sum-1][i][j-1],F[sum-1][i-1][j],F[sum-1][i-1][j-1]};
int main(){
int m,n; scanf("%d%d",&m,&n);
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++) scanf("%d",&a[i][j]);
memset(F,-1,sizeof(F));//赋初值为-1
F[2][1][1]=0; //最初的点,在左上角,好感度为0 [初始化]
for(int k=3;k<m+n;k++) //纵坐标与横坐标的和
for(int i=1;i<n;i++)
for(int j=i+1;j<=n;j++){
int s=F[k][i][j]; //讨论各种情况,更不更新
if(F[k-1][i][j]>s) s=F[k-1][i][j];
if(F[k-1][i-1][j]>s) s=F[k-1][i-1][j];
if(F[k-1][i][j-1]>s) s=F[k-1][i][j-1];
if(F[k-1][i-1][j-1]>s) s=F[k-1][i-1][j-1];
if(s==-1) continue;
//↑↑↑当s为-1时,说明四种情况都不能到该点,故不存在
F[k][i][j]=s+a[k-i][i]+a[k-j][j];
//↑↑↑该点的值为最大的前一个值与当前F[k][i][j]表示两点的值的和
}
printf("%d",F[m+n-1][n-1][n]); //因为i永远小于j,所以右下角的点不会求到
//但是到右下角只有一种情况,在右下角的上面和右下角的左边,直接输出即可
return 0;
}
扫描二维码关注公众号,回复:
2977588 查看本文章
——时间划过风的轨迹,那个少年,还在等你。