2022.2.11解题报告

2022.2.11解题报告

T1.智力测试题

题目描述:

在这里插入图片描述

思路:

对于列和行来说,它们均为以n为周期,在1~n中周期性地出现。
故时间对n取余再加一(因为是从(1, 1)开始的)就是列数,对n取商再取余再加一就是行数(因为直接取商是经过的行数,有可能大于n,即走过了整张图又回到(1, 1)的情况)。

代码:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int n, t;

int main() {
	scanf("%d%d", &n, &t);
	int c = t % n, r = t / n; //c为列,r为行
	r %= n;
	r ++, c ++ ;
	printf("%d %d\n", r, c);
	return 0;
}

T2.采集灵石

题目描述:

在这里插入图片描述

思路:

显然,由于每个岛都是独立的,互相之间没有任何关联的,所以我们只需要将去每一个岛所得的“利润”计算出来,看看是亏还是赚还是不亏不赚,就可以决定去不去这个岛了。
即:
判断 (开采收入 - 途中花费) 与0的大小关系。
在判断出所有需要去的岛后,我们还需要判断牛牛能否前往这个岛(手中的灵石数量是否大于等于途中的花费)。
对此,我们可以将前往每一个岛的途中花费进行排序,如果牛牛连最少的灵石数量都不够,那么他也一定无法支付更多的途中花费,也就不用再分析后面的岛了。

代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#define x first
#define y second
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 1e5 + 10;
int n, k;
PII w[N];

int main() {
	scanf("%d%d", &n, &k);
	int ans = k; //ans用来计算答案,同时动态存储牛牛当前所有的灵石数量
	for (int i = 1 ; i <= n ; i ++ ) {
		int x, y;
		scanf("%d%d", &x, &y);
		int sub = x - y;
		if (sub > 0) //只存储利润大于0的岛
			w[i] = {y, sub}; //pair存储去该岛的途中花费和该岛上的利润
	}
	sort(w + 1, w + n + 1);
	for (int i = 1 ; i <= n ; i ++ ) {
		auto t = w[i];
		if (ans >= t.x)
			ans += t.y;
		else
			break; //如果当前所有的灵石数量不够了,就不用再往下遍历了
	}
	printf("%d\n", ans);
	return 0;
}

T3.跳跃的排列

题目描述:

在这里插入图片描述
在这里插入图片描述

思路:

手动枚举几次,会发现答案总是只有1和0,所以我们就诞生了一个猜想:

本题中,只有“进行一次操作”和“不进行操作”两种情况。

现在我们来证明一下这个猜想:

假设已经进行过一次操作了;
则对于任意一个a[i],它的答案的位置有两种情况:

①在i处,那么可以得出在a[i + 1] ~ a[n]中都没有大于等于a[i]的数了,则再进行一次操			
    作后,a[i]的答案仍在i处。
②在i的右侧,假设在j处,那么则有:
	a[j + 1] ~ a[n]中的所有元素均小于a[i] = a[j]。
	即a[j]是最后一个大于等于a[i]的元素,故再进行一次操作后,a[i]的答案仍为a[j]。
	现在来看看区间[i + 1, j - 1]上的数:
	假设在这个区间上有一个数a[k] (i < k < j),那么它的大小有三种情况:
	
		①a[k] > a[i] = a[j] > a[j + 1] ~ a[n]
			此时a[k]的右侧已经没有任何大于等于a[k]的数了,所以再操作一次后,它的答案
			还是在k处。
		②a[k] = a[i] = a[j]
			由于k在j左侧,所以再操作一次后a[k]的答案仍为a[j] = a[k]。
		③a[k] < a[i] = a[j]
			如果是这样,那么在j的右侧一定存在一个数a[t],且a[t] = a[k]。(a[k]的答案一
			定来源于k的右侧,且不能再k和j之间,否则这个数就一定会在第一次操作中被
			改变),那么a[k]的答案就一定仍为a[t] = a[k]。

综上,本题中的数列只有操作一次与不操作两种情况,而不操作的情况只有单调递减和全部相等两种情况。

代码:

#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int a[N];
int n;
int main(){
    scanf("%d", &n);
    for(int i = 1 ; i <= n ; i ++ ){
        scanf("%d", a + i);
        if(a[i - 1] < a[i] && i != 1){ //如果有不单调递减的,就一定会进行操作
            puts("1");
            return 0;
        }
    }
    puts("0");
    return 0;
}

T4.防御法阵

题目描述:

在这里插入图片描述
在这里插入图片描述

思路:

我们先单独处理每一面城墙。由于每一面城墙是独立的,所以我们可以先求出破坏每一面城墙的法阵时所能得到的最大经验值,然后再来一起计算。

对于每一面城墙,都有它自己的总容量(能停留的时间);而它的每一个法阵都有各自的权重(经验值)和体积(消耗时间)。
看到这里,就能明白了。这是一个经典的01背包问题,我们要用动态规划来进行每一面城墙的预处理。

现在来处理预处理完毕后的计算:

注意到每次选择破坏的城墙都是连在一起的,且长度均为m,所以我们想到了用区间动态规划来完成计算。

状态表示:f[i][j]
状态含义:长度为j,以i号城墙为左端点的区间所能获得的最大经验值
由于本题中,获得的经验值有累积效果,所以我们有从右到左从左到右两种破坏的顺序,由此可以推出状态转移方程:
状态转移方程
f[i][j] = max(2 * f[i + 1][j - 1] + max_exp[i], 2 * f[i][j - 1] + max_exp[i + j - 1])
其中max_exp[i]为i号破坏城墙所能获得的最大经验值。

代码:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1e4 + 10;
int n, m, t;
int f[N]; //背包用dp数组
int maxv[N];
int tw[N], v[N];
LL dp[N][N]; //计算用dp数组

int main() {
	scanf("%d%d%d", &n, &m, &t);
	
	for (int p = 1 ; p <= n ; p ++ ) { //预处理
		int total;
		memset(f, 0, sizeof f);
		scanf("%d", &total);
		for (int i = 1 ; i <= total ; i ++ )
			scanf("%d%d", v + i, tw + i);
		for (int i = 1 ; i <= total ; i ++ )
			for (int j = t ; j >= tw[i] ; j -- )
				f[j] = max(f[j], f[j - tw[i]] + v[i]);
		maxv[p] = f[t];
	}
	
	for (int j = 1 ; j <= m ; j ++ ) //计算
		for (int i = 1 ; i <= n ; i ++ )
			dp[i][j] = max(2 * dp[i + 1][j - 1] + maxv[i], 2 * dp[i][j - 1] + maxv[i + j - 1]);

	LL res = 0; //找最大值
	for (int i = 1 ; i < n ; i ++ )
		res = max(res, dp[i][m]);
	printf("%lld\n", res);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值