第十四届蓝桥杯大赛软件赛省赛(Java 大学B组)

news2024/11/17 9:29:01

目录

  • 试题 A. 阶乘求和
    • 1.题目描述
    • 2.解题思路
    • 3.模板代码
  • 试题 B.幸运数字
    • 1.题目描述
    • 2.解题思路
    • 3.模板代码
  • 试题 C.数组分割
    • 1.题目描述
    • 2.解题思路
    • 3.模板代码
  • 试题 D.矩形总面积
    • 1.问题描述
    • 2.解题思路
    • 3.模板代码
  • 试题 E.蜗牛
    • 1.问题描述
    • 2.解题思路
    • 3.模板代码
  • 试题 F.合并区域
    • 1.题目描述
    • 2.解题思路
    • 3.模板代码
  • 试题 G.买二赠一
    • 1.问题描述
    • 2.解题思路
    • 3.模板代码
  • 试题 H.合并石子
    • 1.问题描述
    • 2.解题思路
    • 3.模板代码
  • 试题 I.最大开支
    • 1.题目描述
    • 2.解题思路
    • 3.模板代码
  • 试题 J. 魔法阵
    • 1.题目描述
    • 2.解题思路
    • 3.模板代码


别问为什么不用 Java 写,Java简直依托答辩
感觉 Java 组难度有点大


试题 A. 阶乘求和

1.题目描述

  令 S = 1 ! + 2 ! + 3 ! + . . . + 202320232023 ! S = 1! + 2! + 3! + ... + 202320232023! S=1!+2!+3!+...+202320232023!,求 S S S 的末尾 9 9 9 位数字。
  提示:答案首位不为 0 0 0

2.解题思路

  阶乘增加很快, 45 ! 45! 45! 的末九位数就全都为 0 0 0 了(当然是Pythond打表看的),因为我们只需要末九位数字,所以后面的数都不会增加贡献,枚举前 45 45 45 位数即可。答案为420940313

3.模板代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int, int> PII;
#define pb(s) push_back(s)
#define sz(s) ((int)s.size())
#define x first
#define y second
#define ms(s,x) memset(s, x, sizeof(s))
#define all(s) s.begin(),s.end()
const int inf = 0x3f3f3f3f;
const int mod = 1000000000;
const int N = 200010;


void solve()
{
    LL x = 1;
    LL ans = 0;
    for (int i = 1; i <= 45; ++i) {
        x *= i;
        x %= mod;
        ans = (ans + x) % mod;
    }
    cout << ans << '\n';
}
int main()
{
    ios_base :: sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    int t = 1;
    while (t--)
    {
        solve();
    }
    return 0;
}

试题 B.幸运数字

1.题目描述

  哈沙德数是指在某个固定的进位制当中,可以被各位数字之和整除的正整数。例如 126 126 126 是十进制下的一个哈沙德数,因为 ( 126 ) 10 m o d    ( 1 + 2 + 6 ) = 0 (126)_{10} \mod (1+2+6) = 0 (126)10mod(1+2+6)=0 126 126 126 也是八进制下的哈沙德数,因为 ( 126 ) 10 = ( 176 ) 8 , ( 126 ) 10 m o d    ( 1 + 7 + 6 ) = 0 ; (126)_{10} = (176)_8,(126)_{10} \mod (1 + 7 + 6) = 0; (126)10=(176)8(126)10mod(1+7+6)=0同时 126 126 126 也是 16 16 16 进制下的哈沙德数,因为 ( 126 ) 10 = ( 7 e ) 16 , ( 126 ) 10 m o d    ( 7 + e ) = 0 。 (126)_{10} = (7e)_{16},(126)_{10} \mod (7+e) = 0。 (126)10=(7e)16(126)10mod(7+e)=0 小蓝认为,如果一个整数在二进制、八进制、十进制、十六进制下均为哈沙德数,那么这个数字就是幸运数字,第 1 1 1 至第 10 10 10 个幸运数字的十进制表示
为: 1 , 2 , 4 , 6 , 8 , 40 , 48 , 72 , 120 , 126... 。 1 , 2 , 4 , 6 , 8 , 40 , 48 , 72 , 120 , 126 . . . 。 1,2,4,6,8,40,48,72,120,126...现在他想知道第 2023 2023 2023 个幸运数字是多少?你只需要告诉小蓝这个整数的十进制表示即可。

2.解题思路

模拟一下。

3.模板代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int, int> PII;
#define pb(s) push_back(s)
#define sz(s) ((int)s.size())
#define x first
#define y second
#define ms(s,x) memset(s, x, sizeof(s))
#define all(s) s.begin(),s.end()
const int inf = 0x3f3f3f3f;
const int mod = 1000000007;
const int N = 200010;

bool check(int x, int v) {
    int g = x;
    int m = 0;
    while (x) {
        m += x % v;
        x /= v;
    }
    return g % m == 0;
}
void solve()
{
    int ans = 0;
    int l = 1;
    while (1) {
        if (check(l, 2) && check(l, 8) && check(l, 10) && check(l, 16)) {
            ans++;
        }
        if (ans == 2023) {
            cout << l << '\n';
            break;
        }
        l++;
    }
}
int main()
{
    ios_base :: sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    int t = 1;
    while (t--)
    {
        solve();
    }
    return 0;
}

试题 C.数组分割

1.题目描述

  小蓝有一个长度为 N N N 的数组 A = [ A 0 , A 1 , . . . , A N − 1 ] A = [A_0, A_1, . . . , A_{N−1}] A=[A0,A1,...,AN1]。现在小蓝想要从 A A A 对应的数组下标所构成的集合 I = { 0 , 1 , 2 , . . . , N − 1 } I = \{ 0, 1, 2, . . . , N − 1\} I={0,1,2,...,N1} 中找出一个子集 R 1 R_1 R1,那么 R 1 R_1 R1 I I I 中的补集为 R 2 R_2 R2。记 S 1 = ∑ r ∈ R 1 A r , S 2 = ∑ r ∈ R 2 A r S_1 =∑_{r∈R1}A_r,S_2 =∑_{r∈R_2}Ar S1=rR1ArS2=rR2Ar,我们要求 S 1 S_1 S1 S 2 S_2 S2 均为偶数,请问在这种情况下共有多少种不同的 R 1 R_1 R1。当 R 1 R_1 R1 R 2 R_2 R2 为空集时我们将 S 1 S_1 S1 S 2 S_2 S2 视为 0 0 0

2.解题思路

  咋感觉大家都用组合数分类讨论,这里用了个简单的动规做法(不会是个假做法吧)。题意无非就是让你将数分为两堆,每堆的和都是偶数,那么显然总和为奇数时即为无解。
  当一堆为偶数时,另一堆一定也为偶数,问题转化为从 n n n 个数中选出若干个数的和为偶数有多少种方案。
  定义 f [ i ] [ 0 ] f[i][0] f[i][0] 为只考虑前 i i i 个数且总和为偶数的方案数, f [ i ] [ 1 ] f[i][1] f[i][1] 为只考虑前 i i i 个数且综合为奇数的方案数。题目允许不选元素,所以初始化 f [ 0 ] [ 0 ] = 1 f[0][0]=1 f[0][0]=1,最终答案即为 f [ n ] [ 0 ] f[n][0] f[n][0]
  状态转移也很简单,当第 i i i 个数为奇数时,选上它奇数序列会变偶数,偶数序列变奇数。当第 i i i 个数为偶数时,偶数序列仍然为偶数序列,奇数序列仍然为奇数序列。
f [ i ] [ 0 ] = { f [ i − 1 ] [ 0 ] + f [ i − 1 ] [ 1 ] a[i]为奇数 f [ i − 1 ] [ 0 ] × 2 a[i]为偶数 f[i][0] = \begin{cases} f[i-1][0]+f[i-1][1] &\text{a[i]为奇数}\\ f[i-1][0] \times 2 &\text{a[i]为偶数}\\ \end{cases} f[i][0]={f[i1][0]+f[i1][1]f[i1][0]×2a[i]为奇数a[i]为偶数
f [ i ] [ 1 ] = { f [ i − 1 ] [ 0 ] + f [ i − 1 ] [ 1 ] a[i]为奇数 f [ i − 1 ] [ 1 ] × 2 a[i]为偶数 f[i][1] = \begin{cases} f[i-1][0]+f[i-1][1] &\text{a[i]为奇数}\\ f[i-1][1] \times 2 &\text{a[i]为偶数}\\ \end{cases} f[i][1]={f[i1][0]+f[i1][1]f[i1][1]×2a[i]为奇数a[i]为偶数

3.模板代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int, int> PII;
#define pb(s) push_back(s);
#define SZ(s) ((int)s.size());
#define ms(s,x) memset(s, x, sizeof(s))
#define all(s) s.begin(),s.end()
const int inf = 0x3f3f3f3f;
const int mod = 1000000007;
const int N = 200010;

int n;
int a[N], f[N][2];
void solve()
{
	cin >> n;
	LL ans = 0;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
		ans += a[i];
	}
	if (ans % 2) {
		cout << 0 << '\n';
		return;
	}
	f[0][0] = 1;
	for (int i = 1; i <= n; ++i) {
		if (a[i] % 2) {
			f[i][0] = (f[i - 1][0] + f[i - 1][1]) % mod;
			f[i][1] = (f[i - 1][1] + f[i - 1][0]) % mod;
		} else {
			f[i][0] = (f[i - 1][0] * 2) % mod;
			f[i][1] = (f[i - 1][1] * 2) % mod;
		}
	}
	cout << f[n][0] << '\n';
}
int main()
{
	ios_base :: sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	int t = 1;
	cin >> t;
	while (t--)
	{
		solve();
	}
	return 0;
}

试题 D.矩形总面积

1.问题描述

  平面上有个两个矩形 R 1 R1 R1 R 2 R2 R2,它们各边都与坐标轴平行。设 ( x 1 , y 1 ) (x1, y1) (x1,y1) ( x 2 , y 2 ) (x2, y2) (x2,y2) 依次是 R 1 R1 R1 的左下角和右上角坐标, ( x 3 , y 3 ) (x3, y3) (x3,y3) ( x 4 , y 4 ) (x4, y4) (x4,y4) 依次是 R 2 R2 R2 的左下角和右上角坐标,请你计算 R 1 R1 R1 R 2 R2 R2 的总面积是多少?
  注意:如果 R 1 R1 R1 R 2 R2 R2 有重叠区域,重叠区域的面积只计算一次。

2.解题思路

正确做法就是容斥,两个矩阵的面积和减去重叠的面积。重叠面积的求法就是求出两个矩阵在 x x x 轴和 y y y 轴的交集。怎么tm是原中原题。

3.模板代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int, int> PII;
#define pb(s) push_back(s)
#define sz(s) ((int)s.size())
#define x first
#define y second
#define ms(s,x) memset(s, x, sizeof(s))
#define all(s) s.begin(),s.end()
const int inf = 0x3f3f3f3f;
const int mod = 1000000007;
const int N = 200010;
 
LL a[8];
void solve()
{   //01 23 45 67
    for (int i = 0; i <= 7; ++i) cin >> a[i];
    LL x = max(0LL, min(a[2], a[6]) - max(a[0], a[4]));
    LL y = max(0LL, min(a[3], a[7]) - max(a[1], a[5]));
    LL res = (a[2] - a[0]) * (a[3] - a[1]) + (a[6] - a[4]) * (a[7] - a[5]) - x * y;
    cout << res << '\n';
}
int main()
{
    ios_base :: sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    int t = 1;
    while (t--)
    {
        solve();
    }
    return 0;
}

试题 E.蜗牛

1.问题描述

  这天,一只蜗牛来到了二维坐标系的原点。在 x x x 轴上长有 n n n 根竹竿。它们平行于 y y y 轴,底部纵坐标为 0 0 0,横坐标分别为 x 1 , x 2 , . . . , x n x_1, x_2, ..., x_n x1,x2,...,xn。竹竿的高度均为无限高,宽度可忽略。蜗牛想要从原点走到第 n n n 个竹竿的底部也就是坐标 ( x n , 0 ) (x_n, 0) (xn,0)。它只能在 x x x 轴上或者竹竿上爬行,在 x x x 轴上爬行速度为 1 1 1 单位每秒;由于受到引力影响,蜗牛在竹竿上向上和向下爬行的速度分别为 0.7 0.7 0.7 单位每秒和 1.3 1.3 1.3 单位每秒。为了快速到达目的地,它施展了魔法,在第 i i i i + 1 i + 1 i+1 根竹竿之间建立了传送门 ( 0 < i < n ) (0 < i < n) 0<i<n,如果蜗牛位于第 i i i 根竹竿的高度为 a i ai ai 的位置 ( x i , a i ) (x_i, a_i) (xi,ai),就可以瞬间到达第 i + 1 i + 1 i+1 根竹竿的高度为 b i + 1 b_i+1 bi+1 的位置 ( x i + 1 , b i + 1 ) (x_i+1, b_i+1) (xi+1,bi+1),请计算蜗牛最少需要多少秒才能到达目的地。

2.解题思路

  显然一根杆我们需要关注它到达转送点的最短时间和底点的最短时间,注意的传送点是从这根杆到下一根杆的传送点,而不是上根杆传送到当前杆的落地点。
  定义 f [ i ] [ 0 ] f[i][0] f[i][0] 表示到达第 i i i 根杆底点的最小时间, f [ i ] [ 1 ] f[i][1] f[i][1] 表示到达第 i i i 根杆传送点的最短时间。底点可以考虑从上一个底点或者上一个传送点转移,传送点也可以考虑从上一个底点或者传送点转移,这里需要注意的是落地点有可能在传送点上,也可能在传送点下,需要分类讨论。
在这里插入图片描述

3.模板代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int, int> PII;
#define pb(s) push_back(s)
#define sz(s) ((int)s.size())
#define x first
#define y second
#define ms(s,x) memset(s, x, sizeof(s))
#define all(s) s.begin(),s.end()
const int inf = 0x3f3f3f3f;
const int mod = 1000000007;
const int N = 100010;

int n;
double x[N], a[N], b[N];
double f[N][2];
void solve()
{
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> x[i];
    for (int i = 1; i < n; ++i) cin >> a[i] >> b[i + 1];
    //地上
    f[1][0] = x[1];
    //杆上的传送点
    f[1][1] = x[1] + (a[1] / 0.7);
    for (int i = 2; i <= n; ++i) {
        //考虑传送点的转移
        f[i][1] = f[i - 1][0] + x[i] - x[i - 1] + (a[i] / 0.7);
        if (b[i] >= a[i]) f[i][1] = min(f[i][1], f[i - 1][1] + (b[i] - a[i]) / 1.3);
        else f[i][1] = min(f[i][1], f[i - 1][1] + (a[i] - b[i]) / 0.7);
        //考虑地上的转移
        f[i][0] = min(f[i - 1][0] + x[i] - x[i - 1], f[i - 1][1] + (b[i] / 1.3));
    }
    cout << f[n][0] << '\n';
}
int main()
{
    ios_base :: sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << setiosflags(ios::fixed) << setprecision(2);
    int t = 1;
    while (t--)
    {
        solve();
    }
    return 0;
}

试题 F.合并区域

1.题目描述

小蓝在玩一款种地游戏。现在他被分配给了两块大小均为 N × N N × N N×N 的正方形区域。这两块区域都按照 N × N N × N N×N 的规格进行了均等划分,划分成了若干块面积相同的小区域,其中每块小区域要么是岩石,要么就是土壤,在垂直或者水平方向上相邻的土壤可以组成一块土地。现在小蓝想要对这两块区域沿着边缘进行合并,他想知道合并以后可以得到的最大的一块土地的面积是多少(土地的面积就是土地中土壤小区域的块数)?
在进行合并时,小区域之间必须对齐。可以在两块方形区域的任何一条边上进行合并,可以对两块方形区域进行 90 度、180 度、270 度、360 度的旋转,但不可以进行上下或左右翻转,并且两块方形区域不可以发生重叠。

2.解题思路

看着像分类讨论的大模拟,暂更。

3.模板代码

试题 G.买二赠一

1.问题描述

  某商场有 N N N 件商品,其中第 i i i 件的价格是 A i Ai Ai。现在该商场正在进行 “买二赠一” 的优惠活动,具体规则是:每购买 2 2 2 件商品,假设其中较便宜的价格是 P P P(如果两件商品价格一样,则 P P P 等于其中一件商品的价格),就可以从剩余商品中任选一件价格不超过 P 2 \frac P2 2P的商品,免费获得这一件商品。可以通过反复购买 2 2 2 件商品来获得多件免费商品,但是每件商品只能被购买或免费获得一次。小明想知道如果要拿下所有商品(包含购买和免费获得),至少要花费多少钱?

2.解题思路

  直观的思路肯定是每次买最大和次大,这样得到的免费购买额度才会尽量大。但如何使用免费机会是关键,我看很多人做法都是去二分标记删除。但其实完全没必要,我们并不需要立刻使用这个免费的机会,我们可以将其存入队列中。
  每次考虑当前最贵的物品,如果队头的免费机会可以买当前物品我们则使用这个机会,否则用钱购买,当用钱购买了两件商品,则向队尾加入第二件商品价格的一半视为一次免费机会,显然这样贪心的做法很正确。

3.模板代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int, int> PII;
#define pb(s) push_back(s);
#define SZ(s) ((int)s.size());
#define ms(s,x) memset(s, x, sizeof(s))
#define all(s) s.begin(),s.end()
const int inf = 0x3f3f3f3f;
const int mod = 1000000007;
const int N = 500010;

int n;
int a[N];
void solve()
{
	cin >> n;
	for (int i = 1; i <= n; ++i) cin >> a[i];
	sort(a + 1, a + n + 1);
	queue<int> q;
	int p = n;
	LL ans = 0;
	while (p >= 1) {
		while (q.size() && p >= 1 && q.front() >= a[p]) {
			q.pop();
			p--;
		}
		//先买第一个
		ans += a[p];
		p--;
		while (q.size() && p >= 1 && q.front() >= a[p]) {
			q.pop();
			p--;
		}
		//再买第二个
		ans += a[p];
		q.push(a[p] / 2);
		p--;
	}
	cout << ans << '\n';
}
int main()
{
	ios_base :: sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	int t = 1;
	while (t--)
	{
		solve();
	}
	return 0;
}

试题 H.合并石子

1.问题描述

在桌面从左至右横向摆放着 N N N 堆石子。每一堆石子都有着相同的颜色,颜色可能是颜色 0 0 0,颜色 1 1 1 或者颜色 2 2 2 中的其中一种。
现在要对石子进行合并,规定每次只能选择位置相邻并且颜色相同的堆石子进行合并。合并后新堆的相对位置保持不变,新堆的石子数目为所选择的两堆石子数目之和,并且新堆石子的颜色也会发生循环式的变化。具体来说:两堆颜色 0 0 0 的石子合并后的石子堆为颜色 1 1 1,两堆颜色 1 1 1 的石子合并后的石子堆为颜色 2 2 2,两堆颜色 2 2 2 的石子合并后的石子堆为颜色 0 0 0。本次合并花费为所选择的两堆石子的数目之和。给出 N N N 堆石子以及他们的初始颜色,请问最少可以将它们合并为多少堆石子?如果有多种答案,选择其中合并总花费最小的一种,合并总花费指的是在所有的合并操作中产生的合并花费的总和。

2.解题思路

代码写炸了,出题人怎么知道我不会区间dp,还好我是C++组的。有心情了再调,就是区间dp裸题的变种,话说隔壁 Python 组还出了个树形 dp 模板题的变种。

3.模板代码

暂更

试题 I.最大开支

1.题目描述

小蓝所在学校周边新开业了一家游乐园,小蓝作为班长,打算组织大家去游乐园玩。已知一共有 N N N 个人参加这次活动,游乐园有 M M M 个娱乐项目,每个项目都需要买门票后才可进去游玩。门票的价格并不是固定的,团购的人越多单价越便宜,当团购的人数大于某个阈值时,这些团购的人便可以免费进入项目进行游玩。这 M M M 个娱乐项目是独立的,所以只有选择了同一个项目的人才可以参与这个项目的团购。第 i 个项目的门票价格 H i Hi Hi 与团购的人数 X X X 的关系可以看作是一个函数:
H i ( X ) = m a x ( K i × X + B i , 0 ) Hi(X) = max (Ki × X + Bi, 0) Hi(X)=max(Ki×X+Bi,0)
max 表示取二者之中的最大值。当 H i = 0 Hi = 0 Hi=0 时说明团购人数达到了此项目的免单阈值。这 N N N 个人可以根据自己的喜好选择 M M M 个娱乐项目中的一种,或者有些人对这些娱乐项目都没有兴趣,也可以选择不去任何一个项目。每个人最多只会选择一个娱乐项目,如果多个人选择了同一个娱乐项目,那么他们都将享受对应的团购价格。小蓝想知道他至少需要准备多少钱,使得无论大家如何选择,他都有能力支付得起所有 N N N 个人购买娱乐项目的门票钱。

2.解题思路

  题意变换过来,就是问你最贵的情况下门票要多少钱,感觉 O ( n 2 ) O(n^2) O(n2) d p dp dp 的话很好写,但这复杂度不行,那就只能硬贪了。每次在已经安排了 x x x 个人的基础上考虑第 x + 1 x+1 x+1 个人,看这 m m m 个项目哪个项目再加一个人能产生的价值更高就把他安排去哪,这个寻找最大价值项目我们可以用优先队列维护起来,每个人都选择当前最优得到答案最优,复杂度 O ( n log ⁡ n ) O(n \log n) O(nlogn)贪心不需要证明的
  需要注意一点的是如果当前最优的项目带来的价值都是负的,我们就不需要再考虑了,因为题意说了可以一个项目都不去。

3.模板代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
#define pb(s) push_back(s)
#define sz(s) ((int)s.size())
#define x first
#define y second
#define ms(s,x) memset(s, x, sizeof(s))
#define all(s) s.begin(),s.end()
const int inf = 0x3f3f3f3f;
const int mod = 1000000007;
const int N = 200010;
 
 
int n, m;
struct node {
	// c 表示当前项目已经放了几个人
    LL k, v, c;
    bool operator<(const node &a) const
    {
        LL x = (a.c + 1) * max(a.k * (a.c + 1) + a.v, 0LL) - a.c * max(a.k * a.c + a.v, 0LL);
        LL y = (c + 1) * max(k * (c + 1) + v, 0LL) - c * max(k * c + v, 0LL);
        return x > y;
    }
};
void solve()
{
    cin >> n >> m;
    priority_queue<node> q;
    for (int i = 1; i <= m; ++i) {
        int k, v;
        cin >> k >> v;
        q.push({k, v, 0});
    }
    for (int i = 0; i < n; ++i) {
        auto a = q.top(); q.pop();
        if ((a.c + 1) * max(a.k * (a.c + 1) + a.v, 0LL) - a.c * max(a.k * a.c + a.v, 0LL) <= 0) {
            q.push({a.k, a.v, a.c});
            break;
        }
        q.push({a.k, a.v, a.c + 1});
    }
    LL ans = 0;
    while (q.size()) {
        auto g = q.top(); q.pop();
        ans += g.c * max(g.k * g.c + g.v, 0LL);
    }
    cout << ans << '\n';
}
int main()
{
    ios_base :: sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    int t = 1;
    while (t--)
    {
        solve();
    }
    return 0;
}

试题 J. 魔法阵

1.题目描述

魔法师小蓝为了营救自己的朋友小 Q Q Q,来到了敌人布置的魔法阵。魔法阵可以看作是一幅具有 N N N 个结点 M M M 条边的无向图,结点编号为 0 , 1 , 2 , . . . , N − 1 0, 1, 2, . . . , N − 1 0,1,2,...,N1,图中没有重边和自环。敌人在每条边上都布置了陷阱,每条边都有一个伤害属性 w w w,每当小蓝经过一条边时就会受到这条边对应的 w w w 的伤害。小蓝从结点 0 0 0 出发,沿着边行走,想要到达结点 N − 1 N − 1 N1 营救小 Q Q Q。小蓝有一种特殊的魔法可以使用,假设一条路径按照顺序依次经过了以下 L L L 条边: e 1 , e 2 , . . . , e L e_1, e_2, . . . , e_L e1,e2,...,eL可以出现重复的边),那么期间小蓝受到的总伤害就是 P = ∑ i − 1 L = w ( e i ) P =\sum_{i-1}^{L}= w(e_i) P=i1L=w(ei) w ( e i ) w(e_i) w(ei) 表示边 e i ei ei 的伤害属性。如果 L ≥ K L ≥ K LK,那么小蓝就可以从这 L 条边当中选出连续出现的 K 条边 e c , e c + 1 , . . . , e c + K − 1 e_c, e_{c+1}, . . . , e_{c+K−1} ec,ec+1,...,ec+K1 并免去在这 K 条边行走期间所受到的伤害,即使用魔法之后路径总伤害变为 P ′ = P − ∑ i = c c + K − 1 = w ( e i ) P′ = P −\sum_{i=c}^{c+K−1}=w(e_i) P=Pi=cc+K1=w(ei)。注意必须恰好选出连续出现的 K K K 条边,所以当 L < K L < K L<K 时无法使用魔法。小蓝最多只可以使用一次上述的魔法,请问从结点 0 0 0 出发到结点 N − 1 N − 1 N1 受到的最小伤害是多少?题目保证至少存在一条从结点 0 0 0 N − 1 N − 1 N1 的路径。

2.解题思路

  题目的意思大概意思就是从 0 0 0 n − 1 n-1 n1 的路径上你可以选择连续的 k k k 条边让它们的伤害不算入答案,问你到达终点受到的最小伤害是多少。
  从我加深的题意来看,我们是可以选择兜圈,使得走的边数达到 k k k 条从而使用魔法让答案更优,所以贪心并不好写。考虑使用 d p dp dp 解决问题,定义 f [ i ] [ j ] f[i][j] f[i][j] 为从点 0 0 0 走到点 i i i 且在这之前已经使用魔法砍掉了 j j j 条边受到的最小伤害。在这我定义在使用魔法的的边上的点为魔法点
  如下图当我们砍掉 1 − 2 1-2 12 2 − 3 2-3 23 3 − 4 3-4 34这三条边时, 1 , 2 , 3 , 4 1,2,3,4 1,2,3,4称为魔法点。
在这里插入图片描述
  类似 d i j k s t r a dijkstra dijkstra 一样,我们用起点不断去迭代到每个点的最短举例。我们假设有从点 u − > v u->v u>v 权值为 w w w 的一条边,分以下三种情况讨论:

  • j j j 等于 0 0 0 时,此时就是没有使用魔法的情况,那我们就正常迭代即可,判断一下 f [ v ] [ 0 ] > f [ u ] + w f[v][0]>f[u]+w f[v][0]>f[u]+w
  • 1 ≤ j ≤ k 1 \leq j \leq k 1jk时,此时应该判断 f [ v ] [ j ] > f [ u ] [ j − 1 ] f[v][j]>f[u][j-1] f[v][j]>f[u][j1],因为题目说了砍边时必须是连续的 k k k 条边,不能前面砍一些后面砍一些,所以此时点 u u u 和点 v v v 一定是一个魔法点,从魔法点走到魔法点的边是不需要记录答案的。
  • j = k j=k j=k 时其实是比较特殊的,此时点 v v v 不一定是魔法点,因为在到达它之前可能我们早就已经砍掉了 k k k 条边。所以这里也需要判断 f [ v ] [ k ] > f [ u ] [ k ] + w f[v][k]>f[u][k]+w f[v][k]>f[u][k]+w

根据上面这三种情况,我们不断更新每个点的最短距离,当然这里考虑的情况比较多,当某个点的任意一个 j j j 的值被更新,我们都应该将其重新加入队列,让它去迭代其他点,可以用数组标记以下某个点是否已经在队列中,减少无效入队次数。每个点反复入队次数不会超过 k k k 次。时间复杂度: O ( n k 2 ) O(nk^2) O(nk2)
  最后答案在 f [ n − 1 ] [ 0 ] f[n-1][0] f[n1][0] f [ n − 1 ] [ k ] f[n-1][k] f[n1][k] 取个较小值,因为可能有路径不用魔法更优。

3.模板代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int, int> PII;
#define pb(s) push_back(s)
#define sz(s) ((int)s.size())
#define x first
#define y second
#define ms(s,x) memset(s, x, sizeof(s))
#define all(s) s.begin(),s.end()
const int inf = 0x3f3f3f3f;
const int mod = 1000000007;
const int N = 1010;
const int M = 15;


int n, m, k;
//到点i且已经去掉了k条边受到的最小伤害
LL f[N][M];
std::vector<PII> e[N];
void solve()
{
	cin >> n >> k >> m;
	for (int i = 0; i < m; ++i) {
		int u, v, w;
		cin >> u >> v >> w;
		e[u].push_back({v, w});
		e[v].push_back({u, w});
	}
	ms(f, inf);
	f[0][0] = 0;
	queue<int> q;
	q.push(0);
	std::vector<int> st(n);
	while (q.size()) {
		int u = q.front(); q.pop();
		st[u] = 0;
		for (auto [v, w] : e[u]) {
			bool flag = false;
			if (f[v][0] > f[u][0] + w) {
				flag = true;
				f[v][0] = f[u][0] + w;
			}
			for (int i = 1; i <= k; ++i) {
				if (f[v][i] > f[u][i - 1]) {
					f[v][i] = f[u][i - 1];
					flag = true;
				}
			}
			if (f[v][k] > f[u][k] + w) {
				f[v][k] = f[u][k] + w;
				flag = true;
			}
			if (flag && !st[v]) {
				st[v] = 1;
				q.push(v);
			}
		}
	}
	cout << min(f[n - 1][0], f[n - 1][k]) << '\n';
}
int main()
{
	ios_base :: sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	int t = 1;
	while (t--)
	{
		solve();
	}
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/486271.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue2加载倾斜摄影

vue3项目加载倾斜摄影 vue3项目加载倾斜摄影的教程可见此人的教程&#xff0c;亲测可用 https://blog.csdn.net/qq_37750030/article/details/124680036 vue2项目加载倾斜摄影 可是为什么到了vue2的老项目里面用不了呢&#xff1f; 原因在于这几个库&#xff0c;全是ts的&…

只出现一次(N次)的数字 / 出现次数最多的数字 / 数组中数字出现的次数

一.题目类型简介 数组中数字出现的次数是一类经典的问题&#xff0c;通常让我们求数组中数字出现的次数及其衍生的问题&#xff0c;比如&#xff0c;只出现一次的数字&#xff0c;只出现两次的数字&#xff0c;在一个数组中只有一个数字出现一次&#xff0c;其他出现两次或者三…

基于FPGA+JESD204B 时钟双通道 6.4GSPS 高速数据采集模块设计(二)研究 JESD204B 链路建立与同步的过程

基于 JESD204B 的采集与数据接收电路设计 本章将围绕基于 JESD204B 高速数据传输接口的双通道高速数据采集实现展 开。首先&#xff0c;简介 JESD204B 协议、接口结构。然后&#xff0c;研究 JESD204B 链路建立与同 步的过程。其次&#xff0c;研究基于 JESD204B …

linux驱动开发 - 10_阻塞和非阻塞 IO

文章目录 1 阻塞和非阻塞 IO1.1 阻塞和非阻塞简介1.2 等待队列1、等待队列头2、等待队列项3、将队列项添加/移除等待队列头4、等待唤醒5、等待事件 1.3 Linux驱动下的poll操作函数 2 阻塞 IO 实验1、驱动程序编写2、编写测试 APP3、编译驱动程序和测试 APP4、运行测试 3 阻塞 I…

elform 动态 rules

一.使用v-for渲染时 前端由于某些需求场景需要&#xff0c;部分表单的渲染是通过 v-for循环渲染显示&#xff0c;此时如何实现表单验证呢&#xff1f;如下&#xff0c;点击第一行的号可以动态的增加更多行表单&#xff0c;不同于单一固定的表单行[参见下文一般情况下]&#xf…

book-riscv-rev1.pdf 翻译(自用)第一章 操作系统接口

Job of operating system: 操作系统使得多个程序分享一台计算机&#xff0c;提供一系列仅靠硬件无法支持的服务。 管理与抽象低级别硬件&#xff08;如&#xff1a;文件处理程序不需要关注使用哪种硬盘&#xff09;使得多个程序分享硬件&#xff08;programs that can run at…

【代码练习】旋转矩阵题解思路记录分析

题目 给你一幅由 N N 矩阵表示的图像&#xff0c;其中每个像素的大小为 4 字节。请你设计一种算法&#xff0c;将图像旋转 90 度。 不占用额外内存空间能否做到&#xff1f; 示例 1: 给定 matrix [ [1,2,3], [4,5,6], [7,8,9] ], 原地旋转输入矩阵&#xff0c;使其变为: [ [7…

【C语言】学习

文章目录 前言1. warm up1.1 输出helloworld1.2 示例1.3 C语言程序结构 前言 以后要学习操作系统深度学习了&#xff0c;所以C语言就不可缺少了。 1. warm up 1.1 输出helloworld #include<stdio.h> void main() {printf("Hello World!!"); }std 标准 io输…

JS案例分析-某国际音x-tt-params参数分析

今天我们要分析的网站是&#xff1a;https://www.tiktok.com/selenagomez?langen&#xff0c;参数名字叫x-tt-params。 先来抓个包 这个接口是用户视频列表url&#xff0c;参数叫x-tt-params&#xff0c;该接口中还有其他参数像msToken&#xff0c;X-Bogus&#xff0c; _sig…

Cartesi 2023 年 4 月回顾

查看你不想错过的更新 2023年5月1日&#xff0c;感谢Cartesi生态系统中所有了不起的构建者&#xff01; 在一个激动人心的旅程之后&#xff0c;我们的首届全球线上黑客马拉松正式结束了&#xff01;有超过200名注册建造者参加&#xff0c;见证了所有参与者展示的巨大才华和奉献…

【Android】串口通信的理论与使用教程

Android系统诞生这十几年以来&#xff0c;Android开发工程师岗位经历了由盛转衰的过程&#xff0c;目前纯UI的Android APP已经鲜有公司愿意花费巨资去开发&#xff0c;Android APP开发的业务也仅剩游戏、物联网&#xff08;Internet of Things&#xff0c;简称IoT&#xff09;等…

springcloud:新一代分布式定时任务框架——PowerJob

0. 引言 之前我们讲解过主流的分布式定时任务框架xxl-job&#xff0c;随着技术的迭代更新&#xff0c;更多的定时任务框架也开始出现&#xff0c;今天我们来看一看新一代的定时任务框架PowerJob 1. PowerJob简介 PowerJob是基于java开发的企业级的分布式任务调度平台&#x…

Java设计原则之单一职责原则、开闭原则、里氏代换原则

文章目录 面向对象设计原则概述 单一职责原则 开闭原则 里氏代换原则 面向对象设计原则概述 软件的可维护性&#xff08;Maintainability&#xff09;和可复用性&#xff08;Reusability&#xff09;是两个非常重要的用于衡量软件质量的属性&#xff0c;软件的可维护性是指软…

Ubuntu常用命令总结

目录 1&#xff09;安装包命令及下载包命令 2&#xff09;阅读协议内容 3&#xff09;执行sh文件&#xff1a; 4&#xff09;创建虚拟环境 5&#xff09;激活虚拟环境 6&#xff09;虚拟环境中安装包 7&#xff09;安装上传代码工具 8&#xff09;代码上传 9&#xff09…

传输控制协议(TCP)知识点总结

文章目录 传输控制协议&#xff08;TCP)知识点总结介绍数据包格式TCP连接的建立和关闭三次握手四次挥手一张图展示这些过程 其他知识 传输控制协议&#xff08;TCP)知识点总结 维基百科: 传输控制协议&#xff08;TCP&#xff09;是Internet协议套件中的主要协议之一。它起源于…

五种最危险的新兴网络攻击技术

SANS研究所的网络专家揭示了包括网络罪犯和民族国家行为者在内的网络攻击者正在使用的五种最危险的新兴网络攻击技术。在旧金山举行的RSA网络安全会议上&#xff0c;由SANS研究所的几位分析师组成的讨论组讨论了新兴的网络攻击战术、技术和程序&#xff0c;并提供了如何为企业做…

使用GLSL来实现实时滤镜的效果

1. 先来明确几个概念 1.1 OpenGL OpenGL 全称为 Open Graphics Library&#xff08;开放图形库&#xff09;。 是用于渲染 2D 或 3D 图像的跨语言跨平台的应用程序编程接口&#xff0c;用于CPU控制GPU做图像渲染&#xff0c;是一套API。 提供设计人员一个共同的硬件驱动标准…

虚函数、静态绑定和动态绑定

静态绑定 class Base { public:Base(int data) :ma(data) {}void show() { cout << "Base::show()" << endl; }void show(int) { cout << "Base::show(int)" << endl; }protected:int ma; };class Derive : public Base { public…

Go | 一分钟掌握Go | 10 - 反射

作者&#xff1a;Mars酱 声明&#xff1a;本文章由Mars酱编写&#xff0c;部分内容来源于网络&#xff0c;如有疑问请联系本人。 转载&#xff1a;欢迎转载&#xff0c;转载前先请联系我&#xff01; 前言 反射你以为只在Java中有吗&#xff1f;Go也有反射机制&#xff0c;很多…

【JavaEE】应用层自定义协议及UDP协议

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE初阶 本篇文章将为大家介绍应用层中UDP协议~~ 在应用层这里&#xff0c;虽然存在一些现有的协议&#xff08;HTTP&#xff09;&#xff0c;但是也有很多情况&#xff0c;需要程序猿自定制协议&a…