PTA 郑州大学2023-2024第一学期算法设计与分析-实验5(第三章)

1. 拦截导弹

还好是弱化了的问题,不然原版导弹拦截还要nlogn的复杂度,还得贪心+二分找。

这道题简化了不少,相信你在上一个实验弄明白后,一眼看出这个的本质也是个最长上升子序列问题。我直接把上次的代码复制粘贴了过来,然后大于号改小于等于,就完成了这道题。

#include<bits/stdc++.h>
//#define int long long
#define endl "\n"

#define fir for(int i=1;i<=n;i++)
#define rif for(int i=n;i>=1;i--)

#define AC return
#define pleaseqwq 0
using namespace std;

typedef pair<int,int>PII;
typedef pair<string,int>PSI;
typedef pair<string,string>PSS;

unordered_map<string,int>si;
unordered_map<int,int>ii;


const int N=2e3+10;

int a[N];
int dp[N];

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(false);
	int n=1;
	while(cin>>a[n]) n++;
	n--;
	//dp[i]表示以a[i]为结尾的最长上升子序列的长度
	//以子序列的结尾位置划分阶段 
	for(int i=1;i<=n;i++){
		int mx=0;
		for(int j=0;j<i;j++){
			if(a[i]<=a[j]) mx=max(mx,dp[j]);
		}
		dp[i]=mx+1;
	}
	int res=-0x3f3f3f3f;
	fir res=max(res,dp[i]);
	cout<<res;
	AC pleaseqwq;
} 

这样便是最长不上升子序列长度

而扩展问题,要用到一个定理,是离散数学中学的。根据偏序图,最长上升子序列的长度即该集合能划分的最长不上升子序列个数。

因此扩展思考题,只需要跑一次最长上升子序列,其个数就是需要配备的个数。

复杂度可以优化为O(nlogn),具体请自行搜题解吧,我没逝

2. 分弹珠

一道dfs

我感觉dp应该也能做,类似于一道,完全背包?

但是数据范围太小了,根据数据范围的话,dfs应该更合适一些

#include<bits/stdc++.h>
//#define int long long
#define endl "\n"

#define fir for(int i=1;i<=n;i++)
#define rif for(int i=n;i>=1;i--)

#define AC return
#define pleaseqwq 0
using namespace std;

typedef pair<int,int>PII;
typedef pair<string,int>PSI;
typedef pair<string,string>PSS;

unordered_map<string,int>si;
unordered_map<int,int>ii;


const int N=2e3+10;

int a[N];
int dp[N];

int dfs(int m,int n){
	//当无弹珠或弹珠只剩1个时,只有一种放法
	if(!m||m==1||n==1){
		return 1;
	}
	//盘子多时,多余的盘子是没用的 
	if(n>m) return dfs(m,m);
	else return dfs(m,n-1)+dfs(m-n,n);
	//否则,要么每个盘子都有,要么至少存在一个盘子为空
	//空递归处理,剩余者仍然是要么全有要么至少一个空 
	//不空时,相当于全放1个,然后递归处理,相当于都没放 
	
	
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(false);
	int m,n;
	//就是一个数字可以被拆分为多少种组合,使得相加仍然为这个数字
	//比如7的话是007,016,025,034,115,124,133,223 
	//数据范围足够小,直接dfs跑一遍应该可以做出来
	int T;
	cin>>T;
	while(T--){
		cin>>m>>n;
		cout<<dfs(m,n)<<endl;
	}
	
	AC pleaseqwq;
} 

3. 让人头疼的“双十一”

经典01背包问题

请自行学习01背包内容,篇幅过多,不便展开。而且学完马上就会这道题了

#include<bits/stdc++.h>
//#define int long long
#define endl "\n"

#define fir for(int i=1;i<=n;i++)
#define rif for(int i=n;i>=1;i--)

#define AC return
#define pleaseqwq 0
using namespace std;

typedef pair<int,int>PII;
typedef pair<string,int>PSI;
typedef pair<string,string>PSS;

unordered_map<string,int>si;
unordered_map<int,int>ii;


const int N=3e3+10;

int v[N],w[N];
int dp[N][N];

int solve(){
	memset(dp,0,sizeof dp);
    memset(v,0,sizeof v);
    memset(w,0,sizeof w);
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++) cin>>v[i];
	for(int i=1;i<=m;i++) cin>>w[i];
	//其中dp[i][j]表示考虑前i个物品,在价格小于等于j时的最大价值 
	for(int i=1;i<=m;i++){
		for(int j=0;j<=n;j++){
			//不买第i个物品相当于考虑前i-1个物品 
			dp[i][j]=dp[i-1][j];
			//否则其对应容量(总价格)减去其所需后对应的最大价值 
			if(j>=v[i]) dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
		}
	}
	/*优化:由于第一维只与上一维状态有关,因此我们可以先滚动数组优化。 
	而滚动数组后相当于一直在两个状态交替,且每个阶段执行了一次状态拷贝
	因此可以再优化
	for(int i=1;i<=m;i++)
		for(int j=n;j>=v[i];j--)
			dp[j]=max(dp[j],dp[j-v[i]]+w[i]); 
	*/
	
	//错半天才发现原来是输出格式错了 
	return dp[m][n];
}


signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(false);
	
	int T;
	cin>>T;
	//经典01背包 
	for(int i=1;i<=T;i++){
		cout<<"Case #"<<i<<": "<<solve()<<endl;
	}
	
	AC pleaseqwq;
} 

离谱的是之前习惯性N=2e3+10,第二维越界了,没报错,是WA

果然数组越界可能会出现各种问题,告诉你RE都是好的,WA让我前前后后思索了好久。。。

4 石子合并

区间dp经典问题

不是,你这01背包后就区间dp,多少有点离谱。换言之,多少沾点(流汗黄豆)

我们考虑最初第l堆石子和第r堆石子合并为一堆,则说明此时l-r之间每堆石子均已合并,这样l和r才能相邻。

即任意时刻,任意一堆石子可以用[l,r]来描述,代表此石子是由最初的l-r堆石子合并而成。

那么一定存在一个k,使在这堆石子形成前,l-k堆石子,k+1-r堆石子合并到一堆

划分点k就是决策,用lr端点以表示整堆石子的状态

于是有状态转移方程

f[l,r]=min(f[l,k],f[k+1,r])+\sum_{i=l}^{r}Ai,其中l<=k<=r

差一个阶段。我们用区间长度作为dp的阶段,当长度到最大,即阶段到最后时,所表示的状态即我们所求。

对于l-r上的任意堆石子合(公式后半段),我们用前缀和处理

#include<bits/stdc++.h>
//#define int long long
#define endl "\n"

#define fir for(int i=1;i<=n;i++)
#define rif for(int i=n;i>=1;i--)

#define AC return
#define pleaseqwq 0
using namespace std;

typedef pair<int,int>PII;
typedef pair<string,int>PSI;
typedef pair<string,string>PSS;

unordered_map<string,int>si;
unordered_map<int,int>ii;


const int N=3e3+10;

int a[N];
int fi[N][N],fm[N][N];
int sum[N];




signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(false);
	
	int n;
	cin>>n;
	fir cin>>a[i];
	fir sum[i]=sum[i-1]+a[i];
	for(int len=2;len<=n;len++){	//阶段 
		for(int l=1;l<=n-len+1;l++){	//状态 
			//右端点
			int r=l+len-1;
			for(int k=l;k<r;k++){
				//决策
				
				if(fi[l][r]==0) fi[l][r]=fi[l][k]+fi[k+1][r];
				else fi[l][r]=min(fi[l][r],fi[l][k]+fi[k+1][r]); 
				fm[l][r]=max(fm[l][r],fm[l][k]+fm[k+1][r]);
			}
			fi[l][r]+=sum[r]-sum[l-1];
			fm[l][r]+=sum[r]-sum[l-1];
		}
	}
	cout<<fi[1][n]<<endl;
	cout<<fm[1][n]<<endl;
	AC pleaseqwq;
} 

5 矩阵取数游戏

你把这个矩阵给斜着转一下

就会发现他本质就是个数字三角形

不仅如此,你再想想杨辉三角

这道题应该是这里面最简单的,你放最后

只要上次实验数字三角形会写,这个就会写

家人们谁懂啊蒸乌鱼

#include<bits/stdc++.h>
//#define int long long
#define endl "\n"

#define fir for(int i=1;i<=n;i++)
#define rif for(int i=n;i>=1;i--)

#define AC return
#define pleaseqwq 0
using namespace std;

typedef pair<int,int>PII;
typedef pair<string,int>PSI;
typedef pair<string,string>PSS;

unordered_map<string,int>si;
unordered_map<int,int>ii;


const int N=3e3+10;

int a[N][N];
int f[N][N];




signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(false);
	
	int n;
	cin>>n;
	fir for(int j=1;j<=n;j++) cin>>a[i][j];
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			f[i][j]=max(f[i-1][j],f[i][j-1])+a[i][j];
		}
	}
	cout<<f[n][n]<<endl;
	AC pleaseqwq;
} 

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值