PTA特别版4

引言

今天学校上机,我将一些思路和大家分享一下

题目五要求:

本题和洛谷P1006一致,感兴趣的同学也可以康康.

在这里插入图片描述

整体思路一:(暴力)DP

A把纸条传递给B,B在传回来的过程,如果我们反着看不就是相当于A同时传递了两张纸条吗,因为我们如果考虑两个方向同时DP,这个复杂度绝必是非常大的,就单单判断二者是不是访问了一个节点就很困难。所以我们不妨反着看,从B到A正是A到B的逆过程,这样,两条路同时进行,一点但同时走到了一个点就只保留一个点的好感度就行,因为只有一条路能访问到这里。

1、定状态

我们同时从(1,1)出发两条路,他们分别走到(i,j)(k,l)所以我们必须表示他们走到哪里了,所以暴力四个状态,前两个表示第一条路走到了哪里,后两个表示另一条路走到哪里。

2、初始化

因为要算好感度值,最开始都没有,所以为0.

3、找转移

记住:找转移的时候抓住“大的区间都是通过小的区间转移得来的再加上他本身的消耗”,本题当走到了(i,j)(k,l)自然的消耗就是a[i][j]+a[k][l],是固定的,所以寻找最优就要看子区间,子区间最优,该区间最优,这就是典型的区间DP,状态转移模板如下:
在这里插入图片描述
再回到这个题,因为dp[i][j][k][h]他的子状态就四个,每个点只能两个方向,向右向下,所以2*2=4个子状态,这里就直接写,不用枚举了。
1、当1节点向右到了本节点时,2节点可能是向下向右来的dp[i - 1][j][k - 1][h] dp[i - 1][j][k][h - 1] 。
2、当1节点向下到了本节点时,2节点可能是向下向右来的dp[i][j - 1][k - 1][h] dp[i][j - 1][k][h - 1] 。
所以这四个子状态中找到最优的解找到就行,再加上本次消耗,所以取最大值。

4、觅答案

当两个点都到达终点即可,即 dp[m][n][m][n]

具体代码

注意,题中说每个节点只能访问一次,所以一旦两个路径当前点冲突了,那么只留下一次的好感度

#include <iostream>
#include <algorithm>
using namespace std;
//暴力DP
int dp[51][51][51][51] = {
    
      };
int a[51][51];//矩阵
int m, n;//行,列
int main()
{
    
    
	cin >> m >> n;
	for (int i = 1; i <= m; i++) {
    
    
		for (int j = 1; j <= n; j++) {
    
    
			scanf("%d", &a[i][j]);
		}
	}
	for (int i = 1; i <= m; i++) {
    
    
		for (int j = 1; j <= n; j++) {
    
    
			for (int k = 1; k <= m; k++) {
    
    
				for (int h = 1; h <= n; h++) {
    
    
					dp[i][j][k][h] = max(max(dp[i - 1][j][k - 1][h], dp[i - 1][j][k][h - 1]), max(dp[i][j - 1][k - 1][h], dp[i][j - 1][k][h - 1])) + a[i][j] + a[k][h];
					//四个子状态选最优(即最大)+本次消耗
					//如果本区间消耗和子区间选取有关系,那么就把本次消耗
					//放进子区间选取中,保证综合下来的结果最优
					if (i == k && j == h) {
    
    //同一点只能走一次
						dp[i][j][k][h] -= a[k][h];//如果访问冲突了,那么好感度只加一次
					}
				}
			}
		}
	}
	cout << dp[m][n][m][n];//两个点都到终点就是结果
}

(所有代码均已运行无误)

经测试,该代码运行情况是(经过多次测试所得最短时间):

在这里插入图片描述

在这里插入图片描述

这个想法很直接很简单,但是时间复杂度至少是O(n^4),也十分消耗空间,做了大量冗余计算,一旦卡数据就完蛋了。

整体思路二:三维DP

我们的上一个方法,第一:是做了很多冗余计算的,当第一条路选择了(i,j),那么第二条路就根本不可能再次去选择(i,j),那么,我们只需要选择剩余可选择的的点就行,来降低复杂度;第二:维度太大了,循环层数太多了,时空复杂度高的一批,所以要降维来降低时空复杂度。

1、定状态

在这里插入图片描述
首先,我们的目标就是减少维度,维度越大,那么时空复杂度就越大,通过上图可知,从源点开始,每次可推进的点必在一个对角线上,和k=i+j是固定的,所以我只需要知道两条路径端点所在的行,就可以通过k推知列号,所以 k,i,j共三个状态。

2、初始化

因为要算好感度值,最开始都没有,所以为0.

3、找转移

记住:找转移的时候抓住“大的区间都是通过小的区间转移得来的再加上他本身的消耗”,本题当走到了(i,k-i)(j,k-j)自然的消耗就是a[i][k - i] + a[j][k - j],是固定的,所以寻找最优就要看子区间,子区间最优,该区间最优,这就是典型的区间DP,状态转移模板如下:
在这里插入图片描述
当走到dp[k][i][j]时,他的子区间是啥,就是上一次推进即k-1的诸多状态,通过上次的诸多状态中找到最优再加之我们本次消耗,就是本次的最优解,注意,i,j是行,在看上一次推进子状态时就是看这两次之间行的关系(行只能向下)

子状态1:

dp[k - 1][i][j]:两次行关系不变

子状态2:

dp[k - 1][i - 1][j]:第一个路径的行下降一个到了i,j不变

子状态3:

dp[k - 1][i][j - 1]:第二个路径的行下降一个到了j,i不变

子状态4:

dp[k - 1][i - 1][j - 1]:第一个路径的行下降一个到了i,第二个路径的行下降一个到了j
综上:在四个子状态中找到最优+自身消耗即可

4、觅答案

在k=m+n-1时,就差一次推进就到了汇点,汇点没有好感度,所以到了倒数第二步就是答案,即dp[m + n - 1][m - 1][m];

具体代码

注:
1、为了减少冗余计算,所以选过了的节点不要重复,所以在实现时候,i行占有了,那么相应的这次推进的点(i,k-i),另一条路径就不要选这个点了,因为他已经帮过一次忙了,另一条路径就要向后枚举还可用的点,所以j=i+1作为初始
2、看上方找状态画的图,k=3的时候,能推进到的列最多k-i=1,所以要越界判定。

#include <iostream>
#include <algorithm>
using namespace std;
//三维DP
int dp[200][200][200] = {
    
    };
int a[51][51];
int m, n;
int main()
{
    
    
	cin >> m >> n;
	for (int i = 1; i <= m; i++) {
    
    
		for (int j = 1; j <= n; j++) {
    
    
			scanf("%d", &a[i][j]);
		}
	}
	
	for (int k = 3; k < m + n; k++) {
    
    //k从3开始到m+n-1,不懂看我上方的图
		for (int i = 1; i <= m; i++) {
    
    //第一路径的行从头到尾都有可能
			for (int j = i + 1; j <= m; j++) {
    
    //为了减少冗余计算,i行走过了,那么我就从j=i+1走
			//这样读者就会有疑问为,i=1时,j不能在1行出现,但是当i=2,j就可以出现在1行了,
			//但是我们换过来想,这不和i=1时重复了吗,因为i,j只是任意的两行,没有
			//先后之分,但为了代码直观,我们默认i在j上面,其实二者没有严格的位置之分
			//所以不能同时让一个人帮两次,所以j在i后面取 
				if (k - i < 1 || k - j < 1) {
    
    //利用列来进行越界判定
					continue;
				}
				dp[k][i][j] = max(max(dp[k - 1][i][j], dp[k - 1][i - 1][j]), max(dp[k - 1][i][j - 1], dp[k - 1][i - 1][j - 1])) + a[i][k - i] + a[j][k - j];
				//四个子状态选最优(即最大)+本次消耗
					//如果本区间消耗和子区间选取有关系,那么就把本次消耗
					//放进子区间选取中,保证综合下来的结果最优
			}
		}
	}
	cout << dp[m + n - 1][m - 1][m];
}

(所有代码均已运行无误)

经测试,该代码运行情况是(经过多次测试所得最短时间):

在这里插入图片描述

在这里插入图片描述(通过这个时间,和上面一比即可看出降了一维,可以将时间复杂度下降非常大的幅度)

降到三维,时间复杂度是O(n^3),就可以大幅减少复杂度,那么我们如果再通过滚动数组就可以再次降维到二维,那么就会更快,但是博主暂时不太会滚动数组,所以这个等以后俺 b 学会了再更新。

题目四要求

在这里插入图片描述

整体思路

这个题就相当于问,给你规定个时间,让你尽可能多的安排人干活,我们按照常识肯定选谁干的快,先选谁,干得快的先干完,然后我才能安排更多的人,这道题就是这种想法。

代码

注:因为要为结构体进行sort,结构体为自定义数据结构,并非内置,所以要告诉sort函数我到底要咋排,所以重载<运算符,告诉我到底咋排序,内部按照什么逻辑,此时<只是个符号,sort会自动调用重载运算符函数。

#include <iostream>
#include <algorithm>
using namespace std;

struct mission {
    
    
	int stime;
	int etime;
	int idx;
	bool operator < (const mission & node) const {
    
    //重载运算符<
		return etime < node.etime;//保证从截止期从小到大
	}
};
mission a[1000];
int answer = 1;
int n;
int main() {
    
    
	cin >> n;
	for (int i = 1; i <= n; i++) {
    
    
		cin >> a[i].stime >> a[i].etime;
	}
	sort(a + 1, a + 1 + n);//将截止期从小到大排序,具体比较方法看重载运算符函数
	int pre = a[1].etime;//最快的一定选,作为前驱,动态寻找个数
	for (int i = 2; i <= n; i++) {
    
    
		int temp = a[i].stime;
		if (temp > pre) {
    
    //如果开始时间大于前驱截止期,就选
			answer++;//更新
			pre = a[i].etime;
		}
		else {
    
    
			continue;
		}
	}
	cout << answer;
}

(所有代码均已运行无误)

经测试,该代码运行情况是(经过多次测试所得最短时间):
在这里插入图片描述

题目三要求

在这里插入图片描述

整体思路

我们就动态的扫描一遍这个数组就行了,边扫描边判断边更新即可。

代码

注:是"前-后"!!!

#include <iostream>
#include <algorithm>
using namespace std;
int n;
int a[10000];//存放目标数据
int ans = 0;
int main() {
    
    
	cin >> n;
	if (n <= 2) {
    
    
		cout << n;
        return 0;
	}
	for (int i = 1; i <= n; i++) {
    
    
		cin >> a[i];
	}
	int pre = a[1] - a[2];//前驱
	int temp_ans = 1;//临时个数,用于记录某一段个数,最后要和答案取最大值
	//因为要的是最大长度
	for (int i = 2; i <= n; i++) {
    
    
		int temp = a[i] - a[i + 1];
		if (i == n) {
    
    //最后一次循环用于更新ans记录用,要不然少更新一次
		//倒数第二次更新完就退出了,没来得及更新ans
			ans = max(ans, temp_ans);
		}
		if (temp*pre < 0) {
    
    //判定是摇摆队列
			pre = temp;//更新
			temp_ans++;
		}
		else {
    
    
			ans = max(ans, temp_ans);//不是的话更新ans
			temp_ans = 1;//这些因为要继续找,所以也要进行更新
			pre = temp;
		}
	}
	cout << ans+1;//最后答案老是少一,所以+1输出
}

(所有代码均已运行无误)

经测试,该代码运行情况是(经过多次测试所得最短时间):
在这里插入图片描述

题目二要求

在这里插入图片描述

整体思路

思路很简单,我贪心最大效益,所以我就让它每次都出现在他的截止期的位置处,如果截止期过大,超过了n,则默认截止期在n即可,如果截止期那个位置处被占用,那么我们就要通过并查集向前寻找他的前驱能插入位置。

代码

注:在实现的时候为了快速向前寻找,所以引入集合树(并查集)思想。

#include <iostream>
#include <algorithm>
using namespace std;

struct mission {
    
    
	int time;//截止期
	int value;//价值
	int idx;//序号
	bool operator < (const mission & node) const {
    
    
		return value > node.value;//这是结构体排序,保证从大到小
	}
};
bool isVisit[1000] = {
    
     false };//判断是否走过这里
mission a[1000];
int answer[1000];//答案
int ano[1000] = {
    
     0 };//并查集
int n;
int val = 0;//总价值
int num = 0;//记录能放进去的总数
int main() {
    
    
	cin >> n;
	for (int i = 1; i <= n; i++) {
    
    
		cin >> a[i].time;
		if (a[i].time > n) {
    
    
			a[i].time = n;
		}
		cin >> a[i].value;//初始化
		a[i].idx = i;
		ano[i] = i;
	}

	sort(a + 1, a + 1 + n);

	for (int i = 1; i <= n; i++) {
    
    
		bool flag = false;
		int item = a[i].time;//查截止期1
		int size = ano[item];//在并查集里面找截止期可能的在答案序列的放入位置
		if (size == 0) {
    
    //是零说明无法放入
			continue;
		}
		if (!isVisit[size]) {
    
    //相应位置可以放进去
			answer[size] = a[i].idx;
			val += a[i].value;
			num = size;
			ano[size] = ano[size - 1];//并查集更新操作
			isVisit[size] = true;
		}
		else {
    
    
			while (1) {
    
    //我找到的位置放不进去,那么
				size = ano[size];//循环在并查集中寻找可以放入的点
				if (size == 0) {
    
    //0说明放不进去了
					flag = true;
					break;
				}
				if (!isVisit[size]) {
    
    
					break;
				}
			}
			if (flag) {
    
    //flag是true说明放不进去
				continue;
			}
			else {
    
    
				answer[size] = a[i].idx;//放进去了和上面操作一样
				val += a[i].value;
				num = size;
				ano[size] = ano[size - 1];
				isVisit[size] = true;
			}
		}
	}
	printf("%d\n", val);
	int i = 1;
	for (; i < num; i++) {
    
    //输出(这里可能有点小错误,大家可以忽略这里,主题还是并查集那块)
		if (answer[i] != 0) {
    
    
			printf("%d ", answer[i]);
		}
	}
	printf("%d", answer[i]);
	system("pause");
}

(所有代码均已运行无误)

经测试,该代码运行情况是(经过多次测试所得最短时间):
在这里插入图片描述

题目一要求

在这里插入图片描述

整体思路

完全背包问题,我为了让效益最大化,肯定优先放入单位体积下价值最大的,所以求一下单位体积价值,排序即可,从大到小放入。

代码

#include <iostream>
#include <algorithm>
using namespace std;

struct mission {
    
    
	double w;//质量
	double v;//价值
	int idx;//序号
	double val;//单位质量价值
	bool operator < (const mission & node) const {
    
    
		return val > node.val;//保证从大到小
	}
};
mission item[1000];
int m, n;
double ans = 0;
double answer[1000] = {
    
     0 };
int main() {
    
    
	cin >> m >> n;
	for (int i = 1; i <= n; i++) {
    
    
		cin >> item[i].w;
		item[i].idx = i;
	}
	for (int i = 1; i <= n; i++) {
    
    
		cin >> item[i].v;
		item[i].val = (item[i].v) / item[i].w;//求一下单位质量价值
	}
	sort(item + 1, item + 1 + n);//将单位质量价值从大到小排序
	for (int i = 1; i <= n; i++) {
    
    
		if (m == 0) {
    
    //背包满了就不装了
			continue;
		}
		double temp_w = item[i].w;//去取出重量
		if (m - temp_w >= 0) {
    
    //可完全放入
			double temp1 = temp_w / item[i].w;//求比值
			answer[item[i].idx] = temp1;
			ans += item[i].v*temp1;//价值*比值(公式见题目)
			m -= temp_w;//更新m
		}
		else {
    
    
			double temp1 = m / item[i].w;//求出我当前能拿走的重量占总体比值
			answer[item[i].idx] = temp1;
			ans += item[i].v*temp1;//用它计算占有总价值的多少
			m = 0;
		}
	}
	printf("%g\n", ans);//输出,可忽略
	int i = 1;
	for (; i < n; i++)
	{
    
    
		printf("%.2f ", answer[i]);
	}
	printf("%.2f", answer[i]);
	system("pause");
	return 0;
}

(所有代码均已运行无误)

经测试,该代码运行情况是(经过多次测试所得最短时间):
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_45678698/article/details/118143093