集美大学第14届蓝桥校选题解

news2024/11/13 21:27:56

本次比赛的出题表如下:
在这里插入图片描述

退役一年,勋总还是那么强呜呜呜
在这里插入图片描述

目录

  • 填空题
  • 签到题
    • [1] JMU最强蓝人
    • [2] 哪有赌狗一直输
    • [3] 元胞自动机
      • 题意
      • 思路
      • 代码实现
        • C++ 实现
        • Java 实现
        • Python 实现
  • 简单题
    • [1] 酒馆战旗
    • [2] 博丽神社例大祭
      • 题意
      • 思路
      • 代码
    • [3] 无所谓,我会出手
      • 题意
      • 思路
      • 代码实现
        • C++ 实现 (1)
        • C++ 实现 (2)
        • Java 实现
        • Python 实现
  • 中档题
  • 难题
    • [1] 导弹拦截特别版
    • [2] Kth-Number
      • 题目陈述
      • 分数设置
      • 算法一:质因数分解(暴力)
        • 算法实现
        • 复杂度分析
      • 算法二:集合/优先队列
        • 思路引入
        • 思路推进
        • 考虑复杂度
        • 实现
      • 算法三:三指针做法+离线
        • 算法思路
        • 代码实现
  • 压轴题:Kth-Wave
    • 题目陈述
    • 算法一:朴素算法(暴力)
      • 算法思路
      • 代码实现
      • 复杂度分析
    • 算法二:数位DP+set维护
      • 算法思路
      • 思路推进
        • 打表
        • 序列的变换——状态转移方程
        • 上升序列的DP方程
          • 对于j==i的变换方式
          • 对于j>i,且i的原位置是山顶
          • 对于j>i,且i的原位置是山谷
      • 再次推进
      • 复杂度分析
    • 代码实现
      • C++
      • python

填空题

[1] 十甚至九

出题人:陈文静

题意

计算 ( h o m o ) 10 = ( 114 ) 5 + ( 141 ) 9 + ( 198 ) 10 (homo)_{10}=(114)_5+(141)_9+(198)_{10} (homo)10=(114)5+(141)9+(198)10 ,其中 ( x ) y (x)_y (x)y 代表数字 x x x 是用 y y y 进制表示的。

思路

按权展开计算十进制结果,并求和。

( 114 ) 5 = ( 1 × 5 2 + 1 × 5 1 + 4 × 5 0 ) 10 = ( 34 ) 10 (114)_5=(1\times5^2+1\times5^1+4\times5^0)_{10}=(34)_{10} (114)5=(1×52+1×51+4×50)10=(34)10

( 141 ) 9 = ( 1 × 9 2 + 4 × 9 1 + 1 × 9 0 ) 10 = ( 118 ) 10 (141)_9 = (1\times9^2+4\times9^1+1\times9^0)_{10}=(118)_{10} (141)9=(1×92+4×91+1×90)10=(118)10

( h o m o ) 10 = ( 34 ) 10 + ( 118 ) 10 + ( 198 ) 10 = ( 350 ) 10 (homo)_{10}=(34)_{10}+(118)_{10}+(198)_{10}=(350)_{10} (homo)10=(34)10+(118)10+(198)10=(350)10

因此 h o m o homo homo 350 350 350

拓展

[蓝桥杯] X X X 进制减法

第十三届蓝桥杯C/C++省赛B组 E题

进制规定了数字在数位上逢几进一。

X X X 进制是一种很神奇的进制,因为其每一数位的进制并不固定!例如说某种 X X X 进制数,最低数位为二进制,第二数位为十进制,第三数位为八进制,则X 进制数 321 321 321 转换为十进制数为 65 65 65

现在有两个 X X X 进制表示的整数 A A A B B B,但是其具体每一数位的进制还不确定,只知道 A A A B B B 是同一进制规则,且每一数位最高为 N N N 进制,最低为二进制。请你算出 A − B A − B AB 的结果最小可能是多少。

请注意,你需要保证 A A A B B B X X X 进制下都是合法的,即每一数位上的数字要小于其进制。

输出一行一个整数,表示 X X X 进制数 A − B A − B AB 的结果的最小可能值转换为十进制后再模 1000000007 1000000007 1000000007 的结果。

实际上进位计数制,更重要的是计数。我们可以根据进位的规则,从 0 0 0 开始不断加 1 1 1 ,根据进位规则产生新的数字。最后我们既可以通过进位表示得出某个数字表示的大小,也可以通过某个数字的大小得到进位表示。

X i X_i Xi X 0 X_0 X0 X 1 X_1 X1 X 2 X_2 X2 X 3 X_3 X3 X 4 X_4 X4 X 5 X_5 X5 X 6 X_6 X6 X 7 X_7 X7 X 8 X_8 X8
B a s e 10 Base_{10} Base10012345678
B a s e 2 Base_2 Base20110111001011101111000
B a s e 5 Base_5 Base50123410111213

现在给定一个数字 X X X ,只要知道 X X X 之前有多少个数字就可以确定 X X X 的大小。

举一个十六进制的例子: ( A B C ) 16 = ( A 00 + B 0 + C ) 16 (ABC)_{16}=(A00+B0+C)_{16} (ABC)16=(A00+B0+C)16 ,假设我们现在想知道 ( A B C ) 16 (ABC)_{16} (ABC)16 在五进制下表示是什么,我们可以考虑 ( A 00 ) 16 , ( B 0 ) 16 , ( C ) 16 (A00)_{16} ,(B0)_{16},(C)_{16} (A00)16,(B0)16,(C)16 分别在五进制下的表示是什么,最后将这三者用五进制加法加起来即可。

对于 ( A 00 ) 16 (A00)_{16} (A00)16 ,我们可以固定最高位为 [ ( 0 ) 16 , ( 9 ) 16 ] [(0)_{16},(9)_{16}] [(0)16,(9)16] 这个范围的数,那么低两位任意取 [ ( 0 ) 16 , ( F ) 16 ] [(0)_{16},(F)_{16}] [(0)16,(F)16] 中的任意值组合一定都小于 ( A 00 ) 16 (A00)_{16} (A00)16,根据计数乘法原理可以知道低两位存在 ( F + 1 ) 16 × ( F + 1 ) 16 (F+1)_{16} \times (F+1)_{16} (F+1)16×(F+1)16 种组合可能,最后再乘上最高位可能取值的数量。 ( A 00 ) 16 (A00)_{16} (A00)16 用十六进制乘法表示就是 ( 9 + 1 ) 16 × ( F + 1 ) 16 × ( F + 1 ) 16 (9+1)_{16} \times (F+1)_{16} \times (F+1)_{16} (9+1)16×(F+1)16×(F+1)16 ,于是可以将乘法的十六进制数转换为五进制数进行乘法就得到了 ( A 00 ) 16 (A00)_{16} (A00)16 在五进制下的表示,即 ( 14 + 1 ) 5 × ( 30 + 1 ) 5 × ( 30 + 1 ) 5 (14+1)_5 \times (30+1)_5 \times (30+1)_5 (14+1)5×(30+1)5×(30+1)5

最终可以得到
( A B C ) 16 = ( A 00 ) 16 + ( B 0 ) 16 + ( C ) 16 = ( 9 + 1 ) 16 × ( F + 1 ) 16 × ( F + 1 ) 16 + ( A + 1 ) 16 × ( F + 1 ) 16 + ( C ) 16 = ( 14 + 1 ) 5 × ( 30 + 1 ) 5 × ( 30 + 1 ) 5 + ( 20 + 1 ) 5 × ( 30 + 1 ) 5 + ( 22 ) 5 \begin{aligned} (ABC)_{16} &= (A00)_{16}+(B0)_{16}+(C)_{16} \\ &= (9+1)_{16}\times (F+1)_{16} \times (F+1)_{16} + (A+1)_{16} \times (F+1)_{16} + (C)_{16} \\ &=(14+1)_5 \times (30+1)_5 \times (30+1)_5 +(20+1)_5 \times (30+1)_5 + (22)_5 \end{aligned} (ABC)16=(A00)16+(B0)16+(C)16=(9+1)16×(F+1)16×(F+1)16+(A+1)16×(F+1)16+(C)16=(14+1)5×(30+1)5×(30+1)5+(20+1)5×(30+1)5+(22)5
根据五进制加法就可以得到五进制的表示,另外乘法本质上也是加法。观察每一个数位,乘的数值始终是一致的,因此可以把这个固定值称为权,以上式子展开的过程称为按权展开。

对于蓝桥杯的这道题,可以先考虑 X X X 进制如何转换为 10 10 10 进制,我们可以参考上面的思考过程。因为每一个数位的进位基数可能是不一样的,因此每一位的权就变为更低位进位基数的连乘积。因为 X X X 进制的每一位的进制基数是不确定的,但是我们知道位权是由更低位进制基数决定的,那么贪心的想,每一位的进位基数尽可能小就可以让数的整体尽可能小,但是基数不能小于或等于 A , B A,B A,B 对应位的最大值(这是进位制的限制)。

此外还需要考虑减法借位是否会影响贪心的策略:[1] 不产生借位,不影响贪心策略 [2] 产生借位,在确定高位进位基数的情况下,借位对高位的影响一致,而当前位进位基数越小借位后的数值越小,且进位基数越小高位的权越小。因此减法借位不会影响贪心策略。

最后生活中常见的 X X X 进制即时分秒,进位基数分别为 24 , 60 , 60 24,60,60 24,60,60

[2] 九大于十

出题人:陈文静

题意

区间 [ 1919 , 114514 ] [1919,114514] [1919,114514] 中的正整数构成数对 < x , y > <x,y> <x,y>,按字典序比较满足 x > y x \gt y x>y 的数对的数量。

思路

可以写一个复杂度为 O ( n 2 ) O(n^2) O(n2) 的程序枚举所有数对,将数字转换为字符串比较并统计答案,C++耗时大概 40 40 40 分钟左右。

也可以考虑先对 n n n 个数字按字典序排序,时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn),那么字典序大于排序后第 i i i 个数字的有 n − i n-i ni 个,最后求和即可。

可以考虑:对于 n n n 个数字,一共进行 n × n n \times n n×n 次比较,其中 n n n 个数对的比较是相等的。剩下 n × n − n n \times n - n n×nn 个数对一半是满足小于关系,一半满足大于关系,因此满足大于关系的数量为 n × n − n 2 \frac{n \times n - n}{2} 2n×nn ,代入公式时间复杂度 O ( 1 ) O(1) O(1)

区间长度 n = 114514 − 1919 + 1 = 112596 n = 114514-1919+1=112596 n=1145141919+1=112596 ,因此最终答案为 112596 × 112595 2 = 6338873310 \frac{112596 \times 112595}{2}=6338873310 2112596×112595=6338873310

C++计算时需要注意类型,答案超过 int 范围。

[3] N皇后

出题人:林贝宁

使用深度优先搜索算法先写出代码,然后用代码跑出答案即可,最后答案为2279184。因为是填空题没有时间限制要求,所以不需要状态压缩优化也可以在2分钟内跑出来。

下面是我用JAVA写的状态压缩版dfs搜索的代码,语法大致同C++,供参考

import java.util.*;

public class Main {
    private static int n, allOne, answerNumber;
    // 三个变量分别表示:n个皇后问题, 一行n个位置棋盘全部赋1的状态, 解的数量

    public static void findAnswer(int[] chessBoard, int row, int nowState, int mainDiagonal, int deputyDiagonal) {
        if (row == n) //找到一组合法解
        {
            answerNumber ++ ;
            return;
        }
        int remainingColumn = allOne & (~ (nowState | mainDiagonal | deputyDiagonal));
        //剩余的还能填写的列 = n个1 - (已经填写的列+主副对角线被占用的位置)
        int nowPosition;
        // 当前需要填写的位置状态
        int nextState, nextMain, nextDeputy;
        //下一层搜索,列被占用的状态,主对角线占用状态,副对角线占用状态
        for (int i = 0; i < n; i++) {
            if (((remainingColumn >> i) & 1) == 0) //当前行第i个位置不能填
                continue;
            nowPosition = 1 << i; //第i个位置的位状态
            chessBoard[row] = i; //第row行,皇后放在第i列的位置上面
            nextState = nowState | nowPosition;
            //下一层搜索,列被占用的状态
            nextMain = (mainDiagonal | nowPosition) >> 1;
            //下一层搜索,主对角线占用状态
            nextDeputy = (deputyDiagonal | nowPosition) << 1;
            //下一层搜索,副对角线占用状态
            findAnswer(chessBoard, row + 1, nextState, nextMain, nextDeputy);
            //进入下一层搜索
        }
        return;
    }

    public static void main(String[] args) {
        Scanner readScanner = new Scanner(System.in);
        n = readScanner.nextInt();
        allOne = (int) (1L << n) - 1; 

        int[] chessBoard = new int[n]; //chessBoard[i]表示第i行,皇后摆放在当前行第几个位置
        findAnswer(chessBoard, 0, 0, 0, 0);
        if(answerNumber == 0)
        {
            System.out.println("No Solution"); //无解
            System.exit(0); //直接结束程序
        }
        System.out.println("There are a total of " + answerNumber + " answers"); //有多少组解
    }
}

签到题

[1] JMU最强蓝人

出题人 :林贝宁

输出YES即可。

#include <bits/stdc++.h>
int main()
{
    std::cout << "YES" << std::endl;
    return 0;
}

[2] 哪有赌狗一直输

出题人:吴杰

当市场价 s i s_i si 大于成本 X X X 时,累加概率。最后输出时特判。

#include<bits/stdc++.h>
using namespace std;
const int N = 104;

int p[N], s[N];

int main(){
    int n,X;cin>>n>>X;
    for(int i=1;i<=n;i++) cin>>p[i];
    for(int i=1;i<=n;i++) cin>>s[i];
    int ans=0;
    for(int i=1;i<=n;i++){
        if(s[i]>X) ans+=p[i];
    }
    if(ans==0) cout<<"shu ma le"<<endl;
    else if(ans==100) cout<<"ying ma le"<<endl;
    else cout<<ans<<endl;
    return 0;
}

[3] 元胞自动机

出题人:陈文静

题意

给一个长度为 n n n 的一维元胞自动机和初始序列,元胞的染色情况由元胞数值的奇偶决定,问 T = t T=t T=t 时刻元胞自动机的染色情况

思路

按题意模拟即可,直接累加会爆int类型,但溢出不影响奇偶性。

也可以对三个数奇偶性进行讨论,若出现三个奇数相加结果仍然奇数,两个奇数一个偶数相加结果为偶数,一个奇数两个偶数相加结果为奇数,三个偶数相加结果为偶数。序列初始值为 0 0 0 1 1 1 ,上面的讨论相当于是异或运算,因此也可以将加法改为异或。

状态更新取决于上个状态,因此不能直接用一维数组模拟,可以使用二维数组模拟,使用滚动数组对二维数组进行空间优化。

代码实现

C++ 实现

#include <bits/stdc++.h>
using namespace std;
int S[1001][101];
int main()
{
    int n, t;
    cin >> n >> t;
    for (int i = 1; i <= n; i++) 
        cin >> S[0][i]; /*初始状态*/
    for (int i = 1; i <= t; i++)
        for (int j = 1; j <= n; j++)
            S[i][j] = S[i - 1][j - 1] ^ S[i - 1][j] ^ S[i - 1][j + 1]; /*异或 / 加法都可以*/
    for (int i = 1; i <= n; i++)
        cout << (S[t][i] ? "B" : "W"); /*输出最终状态染色情况*/
    return 0;
}

Java 实现

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int t = in.nextInt();
        int[][] S = new int[1001][101];
        for (int i = 1; i <= n; i++) {
            S[0][i] = in.nextInt();
        }
        for (int i = 1; i <= t; i++) {
            for (int j = 1; j <= n; j++) {
                S[i][j] = S[i - 1][j - 1] ^ S[i - 1][j] ^ S[i - 1][j + 1];
            }
        }
        for (int i = 1; i <= n; i++)
            System.out.print(S[t][i] == 1 ? "B" : "W");
        in.close();
    }
}

Python 实现

n, t = map(int, input().split())

S = [[0 for i in range(0, 101)] for j in range(0, 1001)]
S[0] = [0]+list(map(int, input().split()))+[0]
for i in range(1, t+1):
    for j in range(1, n+1):
        S[i][j] = S[i-1][j-1] ^ S[i-1][j] ^ S[i-1][j+1]
for i in range(1, n+1):
    if S[t][i]:
        print("B", end='')
    else:
        print("W", end='')

简单题

[1] 酒馆战旗

出题人: 吴杰

瓶盖兑矿泉水炉石版,虽然游戏马上要寄了

因为 Z < Y Z<Y Z<Y,答案一定是下降的,直接模拟即可。

#include<bits/stdc++.h>
using namespace std;

int main(){
    int T;cin>>T;
    while(T--){
        int X,Y,Z;cin>>X>>Y>>Z;
        int ans=0;
        while(X>=Y){ // 能购买随从
            int t=X/Y; // 买入 t 个随从, 自动下取整
            X%=Y; // 剩余铸币, 写 X-=t*Y 也行
            ans+=t; // 统计答案
            X+=t*Z; // 把买入的 t 个随从卖出获得 t*Z 铸币
        }
        cout<<ans<<endl;
    }
    return 0;
}

[2] 博丽神社例大祭

题意

出题人:蔡培伟

在一个 n × n n \times n n×n 的矩阵内给定 k k k 个特殊点,问所有点到特殊点的最短距离的总和是多少。

设矩阵上两个点的位置为 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1, y_1),(x_2,y_2) (x1,y1),(x2,y2) ,那么点与点之间之间的距离为 ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ |x_1 - x_2| + |y_1 - y_2| x1x2+y1y2

思路

知识点:多源 BFS 。

假设我们只有一个点 v v v ,从这个点开始进行 bfs ,就像类似于从一个着火点开始,逐步蔓延至周边的其他位置,搜索到的点顺序是按照其距离源点 v v v 的距离由近到远进行排序。

在这道题中有多个源点,我们只需要将这几个点加入 bfs 的队列,直接进行 bfs ,就能得到每个点到其最近源点的距离,累加起来就是答案了。

代码

c++ 代码。

#include <bits/stdc++.h>
using namespace std;
template <class T> constexpr T inf = std::numeric_limits<T>::max() / 2;
using ll = long long;

int main() {
	std::cin.tie(nullptr)->sync_with_stdio(false);
	int n, k;
	cin >> n >> k;
	vector a(n + 2, vector<bool>(n + 2));
	vector dis(n + 2, vector<int>(n + 2, inf<int>));
	for (int i{}; i <= n + 1; i ++) {
		a[i][0] = a[0][i] = a[n + 1][i] = a[i][n + 1] = true;  // 标记边界
	}
	queue<pair<int,int>> q;
	// 标记源点并加入队列中
	for (int i{}; i < k; i ++) {
		int x, y;
		cin >> x >> y;
		a[x][y] = true;
		dis[x][y] = 0;	// 源点距离初始化为 0
		q.emplace(x, y);
	}
	// 四个方向
	vector<pair<int,int>> D = {
		{-1, 0}, {1, 0}, {0, -1}, {0, 1}
	};
	// BFS
	while (!q.empty()) {
		auto [x, y] = q.front();
		q.pop();
		for (auto [dx, dy] : D) {
			int nx = dx + x, ny = y + dy;
			if (dis[nx][ny] == inf<int> && !a[nx][ny]) {
				dis[nx][ny] = dis[x][y] + 1;
				q.emplace(nx, ny);
			}
		}
	}
	// 统计答案
	ll ans{};
	for (int i = 1; i <= n; i ++) {
		for (int j = 1; j <= n; j ++) {
			ans += dis[i][j];
		}
	}
	cout << ans << '\n';
}

java 代码。

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

class Info {
    Info() {}
    Info(int x, int y) { this.x = x; this.y = y; }
    public int x, y;
}

public class Main {
	static Scanner cin = new Scanner(System.in);
    static PrintWriter cout = new PrintWriter(System.out);
    public static void main(String[] args) {
        Solve();
        cin.close();
        cout.close();
    }
    public static void Solve() {
        int n = cin.nextInt(), k = cin.nextInt();
        int[][] dis = new int[n + 2][n + 2];
        for (int i = 1; i <= n; i ++) {
            dis[i][0] = dis[0][i] = dis[n + 1][i] = dis[i][n + 1] = 0;
            for (int j = 1; j <= n; j ++) {
                dis[i][j] = Integer.MAX_VALUE;
            }
        }
        Queue<Info> q = new LinkedList<>();
        ArrayList<Info> D = new ArrayList<>();
        D.add(new Info(-1, 0));
        D.add(new Info(1, 0));
        D.add(new Info(0, -1));
        D.add(new Info(0, 1));
        while (k --> 0) {
            int x = cin.nextInt(), y = cin.nextInt();
            dis[x][y] = 0;
            q.offer(new Info(x, y));
        }
        while (!q.isEmpty()) {
            var tmp = q.poll();
            int x = tmp.x, y = tmp.y;
            for (var dd : D) {
                int nx = x + dd.x, ny = y + dd.y;
                if (dis[nx][ny] == Integer.MAX_VALUE) {
                    dis[nx][ny] = dis[x][y] + 1;
                    q.offer(new Info(nx, ny));
                }
            }
        }
        long ans = 0;
        for (int i = 1; i <= n; i ++) {
            for (int j = 1; j <= n; j ++) {
                ans += dis[i][j];
            }
        }
        cout.println(ans);
    }
}

py代码。

import queue

inf = 1 << 30

def Solve() -> None:
    n, k = map(int, input().split())
    a = [[False] * (n + 2) for i in range(n + 2)]
    dis = [[inf] * (n + 2) for i in range(n + 2)]
    q = queue.Queue()
    for i in range(0, n + 2):
        dis[i][0] = dis[0][i] = dis[n + 1][i] = dis[i][n + 1] = 0
    for i in range(k):
        x, y = map(int, input().split())
        a[x][y] = True
        dis[x][y] = 0
        q.put((x, y))
    D = [
        (-1, 0), (1, 0), (0, -1), (0, 1)
    ]
    while not q.empty():
        x, y = q.get()
        for dx, dy in D:
            nx = x + dx; ny = y + dy
            if dis[nx][ny] == inf:
                dis[nx][ny] = dis[x][y] + 1
                q.put((nx, ny))
    ans = 0
    for i in range(1, n + 1):
        for j in range(1, n + 1):
            ans += dis[i][j]
    print(ans)
    return None

if __name__ == '__main__':
    Solve()

[3] 无所谓,我会出手

出题人:陈文静

题意

一天可以建造一台机器,若机器在第 x x x 天建造,第 x + 1 x+1 x+1 0 0 0 点运行,并于第 x + d + 1 x+d+1 x+d+1 0 0 0 点报废,建造机器时刻的取值范围为 [ 0 , + ∞ ) [0,+ \infin) [0,+)。给出 n n n 个不同的时刻,每个时刻都需要有至少 a a a 台机器同时工作,问是否存在一种方案建造机器使得对于给出的 n n n 个时刻至少都有 a a a 台机器同时工作,如果存在至少需要多少台。

思路

t t t 天建造的机器只在 [ t + 1 , t + d + 1 ) [t+1,t+d+1) [t+1,t+d+1) 时间段工作。

对于第 t i t_i ti 天,我们只需要检查 [ max ⁡ ( 0 , t i − d ) , t i − 1 ] [\max(0,t_i-d),t_i-1] [max(0,tid),ti1] 天建造的机器数量,如果数量不足 a a a ,可以利用贪心思想从后往前依次枚举可以建造的时刻补充机器至 a a a 台并标记补充建造的时刻(贪心:建造机器的时间尽可能靠后)。同时只需要满足 a ≤ d a \le d ad a ≤ t 1 a \le t_1 at1 一定存在解,当然也可以在枚举的过程中判断是否有解。

排除无解情况以后,可以考虑第 t i t_i ti 天, [ t i − 1 , t i ) [t_{i-1} ,t_i) [ti1,ti) 区间在考虑第 t i t_i ti 天之前不可能建造过机器,最坏情况是这个区间都建造机器才能满足第 t i t_i ti 天同时工作机器数量大于或等于 a a a。因此我们可以维护建造时刻的队列 q q q,队首建造时刻保持机器对 t i t_i ti 时刻有效,需要增加机器的数量即 max ⁡ ( 0 , a − q . s i z e ( ) ) \max(0,a-q.size()) max(0,aq.size()) ,且增加机器建造的时间为 [ t i − 1 , t i ) [t_{i-1},t_i) [ti1,ti) 区间从后往前连续的天数(贪心)。

本题定位为简单题,没有卡掉前一种做法,只需要想到贪心思路即可通过本题,且Java和Python(pypy3)使用第一种做法也可以通过。

代码实现给出C++两种做法实现、Java和Python第二种做法的实现。

当然本题输出 No 可以骗两分。虽然一般比赛会避免只输出一个 Yes / No 的情况,但是如果写不出正解,可以尝试写一些朴素算法或者判断一些特殊情况骗分 (仅限于有部分分数的比赛)。

代码实现

C++ 实现 (1)

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
int T[maxn];
int main()
{
    int n, a, d;
    cin >> n >> a >> d;
    /*红色怪鱼的数量*/
    /*击败红色怪鱼所需同时工作机器的数量*/
    /*机器报废的天数*/
    bool flag = true; /*是否存在解*/
    int ans = 0;      /*答案统计*/
    for (int i = 1; i <= n && flag; i++)
    {
        int t, sum = 0; /*第i只红色怪鱼出现的时刻*/
        cin >> t;
        for (int j = 1; j <= d; j++) /*计算对时刻t有效的机器数量*/
        {
            if (t - j >= 0) /*t-j即建造机器的时刻*/
            {
                sum += T[t - j]; /*累积机器数量*/
            }
        }
        for (int j = 1; j <= d && sum < a; j++) /*对时刻t有效的机器数量不够增加机器*/
        {
            if (t - j < 0) /*无法继续建造*/
            {
                flag = false; /*无解*/
            }
            else if (!T[t - j]) /*t-j时刻没建造过机器*/
            {
                ans++;        /*建造一台机器*/
                sum++;        /*增加对时刻t有效的机器数量*/
                T[t - j] = 1; /*t-j时刻建造一台机器*/
            }
        }
        if (sum < a) /*机器数量仍然不够*/
        {
            flag = false; /*无解*/
        }
    }
    if (flag) /*输出答案*/
    {
        cout << "Yes\n";
        cout << ans;
    }
    else
    {
        cout << "No";
    }
    return 0;
}

C++ 实现 (2)

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int n, a, d, t;
    cin >> n >> a >> d >> t;
    /*红色怪鱼的数量*/
    /*击败红色怪鱼所需同时工作机器的数量*/
    /*机器报废的天数*/
    /*第一只红色怪鱼到达的时间*/
    if (a > d || a > t) /*无解情况*/
    {
        cout << "No";
    }
    else
    {
        int ans = 0;  /*统计建造数量*/
        queue<int> q; /*建造时间队列*/
        for (int i = t - a; i < t; i++)
            q.push(i), ans++; /*处理第一只红色怪鱼所需机器*/
        for (int i = 1; i < n; i++)
        {
            cin >> t;                               /*对剩下n-1只红色怪鱼到达时间处理*/
            while (!q.empty() && q.front() + d < t) /*队列不空且队首建造时间对t时刻无效*/
                q.pop();                            /*弹出队首元素直到满足对t时刻有效*/
            int add = a - q.size();                 /*需要增加机器的数量*/
            for (int i = t - add; i < t; i++)       /*从后t-1时刻往前贪心建造机器,但维护队列需要从前往后添加*/
                q.push(i), ans++;                   /*新建机器*/
        }
        cout << "Yes\n"; /*输出答案*/
        cout << ans;     /*输出答案*/
    }
    return 0;
}

Java 实现

import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class Main {
	static Scanner cin = new Scanner(System.in);
	static PrintWriter cout = new PrintWriter(System.out);
	public static void main(String[] args) {
		Solve();
		cin.close();
		cout.close();
	}
	public static void Solve() {
		int n = cin.nextInt(), a = cin.nextInt(), d = cin.nextInt(), t = cin.nextInt();
		if (a > d || a > t) {
			cout.println("No");
			return;
		}
		int ans = 0;
        Queue<Integer> q = new LinkedList<>();
		for (int i = t - a; i < t; i ++) {
			q.add(i);
			ans ++;
		}
		for (int i = 1; i < n; i ++) {
			t = cin.nextInt();
			while (!q.isEmpty() && Integer.compare(q.peek() + d, t) == -1) {
				q.poll();
			}
			int add = a - q.size();
			for (int j = t - add; j < t; j ++) {
				q.add(j);
				ans ++;
			}
		}
		cout.println("Yes");
		cout.println(ans);
	}
}

Python 实现

from collections import deque

def Solve() -> None:
    n, a, d = map(int, input().split())
    t = int(input())
    if a > d or a > t:
        print("No")
        return
    ans = 0
    q = deque()
    for i in range(t - a, t, 1):
        q.append(i); ans += 1
    for i in range(1, n, 1):
        t = int(input())
        while len(q) > 0:
            tmp = q.popleft()
            if tmp + d >= t:
                q.appendleft(tmp)
                break
        add = a - len(q)
        for j in range(t - add, t, 1):
            q.append(j); ans += 1
    print("Yes")
    print(ans)
    return None

if __name__ == '__main__':
    Solve()

中档题

[1] 数据结构大师

出题人:周鑫

60 60 60分做法

利用C++中的set直接进行模拟可以得到 60 60 60的分数。

但是为什么只有 60 60 60分?

科普一下set中“==”的逻辑:

  1. 若两个集合的大小不相同,则返回false
  2. 而后对两个集合同时进行遍历,进行逐个比对,若出现两个元素不相同,则返回false,若全部元素均相同,则返回true。

也就是说,在最坏情况下,两个集合进行比较的时间复杂度是 O ( n ) O(n) O(n)的。故用这个做法本题最坏的时间复杂度是 O ( n 2 ) O(n^2) O(n2),不能通过此题。

100 100 100分解法 1 1 1

回忆一下高中乃至小学学过的知识:如何判断两个数或两个式子相等?

一个显然的想法是:对两个式子做差,如果差值为 0 0 0,则两个式子相等。

我们利用这个思想来解决这个问题。

显然判断一个集合是否为空集是简单的:只要判断该集合大小是否为 0 0 0即可。

我们令集合 S S S
S = ( A ∪ B ) − ( A ∩ B ) S = (A \cup B) - (A \cap B) S=(AB)(AB)

其中 A , B A,B A,B为题意中所表示的两个集合

显然,若 S S S是空集,那么 A = B A=B A=B

( A ∪ B ) − ( A ∩ B ) = ∅ → ( A ∪ B ) = ( A ∩ B ) → A = B \begin{aligned} &(A \cup B) - (A \cap B) = \varnothing \\ \rightarrow &(A \cup B) = (A \cap B)\\ \rightarrow& A = B \end{aligned} (AB)(AB)=(AB)=(AB)A=B

考虑维护这个集合 S S S

如何维护? 看下面这个例子。

现在我们需要将数 x x x插入 A A A中,在插入前,会有以下两种情况中的其中一种发生

  1. x ∈ A x \in A xA
  2. x ∉ A x \not\in A xA

对于第一种情况 我们忽略即可。

那么对于第二种情况,则也有以下两种情况的其中一种发生

  1. x ∈ S x \in S xS
  2. x ∉ S x \not\in S xS

由于 x ∉ A x \not\in A xA,则如果第一种情况发生,则是 x ∈ B x \in B xB,按照 S S S的定义我们把 x x x S S S中移去;如果是第二种情况发生,则是 x ∉ B x \not\in B xB发生,则我们将 x x x插入 S S S中即可。

对于每次查询,只需要判断 S S S是否为空集即可。

时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int Q;
    cin >> Q;
    vector<set<int>> s(2);//s[0] 表示 A,s[1] 表示 B.
    set<int> S;
    while (Q--)
    {
        int op;
        cin >> op;
        if (op == 1 || op == 2)
        {
            op--;
            int x;
            cin >> x;
            if (s[op].count(x))
                continue;
            if (S.count(x))
                S.erase(x);
            else
                S.insert(x);
            s[op].insert(x);
        }
        else
        {
            if (S.empty())
                cout << "Yes" << endl;
            else
                cout << "No" << endl;
        }
    }
    return 0;
}

100 100 100分解法 2 2 2

回忆一下我们在 60 60 60分做法中遇到的困难:set直接比较的复杂度太高,无法直接比较。

而判断两个整数相等的时间复杂度是 O ( 1 ) O(1) O(1),那么我们自然而然就有一个想法:能否把一个集合“转换成一个整数”,然后进行比较?

在本题中想完美的解决这个问题是很困难的,幸运的是,在允许一定程度的错误的情况下,这个问题是很简单的。

考虑这样一个函数
f : S → N f: S \rightarrow \text{N} f:SN

它满足以下两个性质:

  1. A = B A=B A=B,则 f ( A ) = f ( B ) f(A) =f(B) f(A)=f(B)
  2. f ( A ) = f ( B ) f(A)=f(B) f(A)=f(B),则 A A A B B B大概率相等

用人话说, f ( S ) f(S) f(S)是一个哈希函数。

故我们只要对集合进行哈希,每次查询时判断二者的哈希值是否相等即可。

至于哈希函数的设计,有许多方法,这里不在赘述。

利用哈希,时间复杂度可以降到 O ( n ) O(n) O(n)

以下是参考代码

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define endl '\n'
struct set_hash
{
    LL val;
    static const LL num1 = 114514, num2 = 1919810;
    static const LL mod = 12346789000;
    set_hash(LL val = 0) : val(val){};

    void insert(LL x)
    {
        LL num = (x + num1) * (x + num2);
        val = (val + num) % mod;
    }
    
    bool operator==(const set_hash &rhs) const {return (val == rhs.val);}
};
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int Q;
    cin >> Q;
    vector<unordered_set<int>> s(2);
    vector<set_hash> h(2);
    while (Q--)
    {
        int op;
        cin >> op;
        if (op == 1 || op == 2)
        {
            op--;
            int x;
            cin >> x;
            if (s[op].count(x))
                continue;
            s[op].insert(x);
            h[op].insert(x);
        }
        else
        {
            if (h[0] == h[1])
                cout << "Yes" << endl;
            else
                cout << "No" << endl;
        }
    }
    return 0;
}

[2] 小学计算题

出题人:周鑫

题意

求数组长度为 n n n b b b的个数

∑ i = 1 n a i b i = K − a 0 \sum_{i=1}^{n} a_{i}b_{i}=K - a_{0} i=1naibi=Ka0
其中 b i ∈ { − 1 , 1 } b_{i} \in \{-1,1\} bi{1,1}

做法

01 01 01背包变形。

b i b_{i} bi只要两种决策( − 1 -1 1 1 1 1), 01 01 01背包也只有两种决策(取或不取)。

考虑动态规划。

d p [ i ] [ j ] dp[i][j] dp[i][j]为考虑到第 i i i个数,且当前值为 j j j的方案数,不难写出状态转移方程

d p [ i ] [ j ] = d p [ i − 1 ] [ j − a [ i ] ] + d p [ i − 1 ] [ j + a [ i ] ] dp[i][j] = dp[i-1][j - a[i]] + dp[i-1][j + a[i]] dp[i][j]=dp[i1][ja[i]]+dp[i1][j+a[i]]

第一个是选择了 + 1 +1 +1的决策,第二个是选择了 − 1 -1 1的决策。

显然答案为 d p [ n ] [ K − a [ 0 ] ] dp[n][K-a[0]] dp[n][Ka[0]]

但是直接这么做会有一个问题:下标会是负数。

有两种方案可以解决这个问题:

  1. 将第二维的下标加一些值使最小值也能大于 0 0 0(加偏移)。
  2. 利用 m a p map map来允许访问负下标,缺点是时间复杂度会多个 log ⁡ \log log

时间复杂度为 O ( n × ( n × m a x ( ∣ a i ∣ ) + K ) ) O(n\times (n\times \mathcal{max}(|a_i |)+ K)) O(n×(n×max(ai)+K))

值得一提的是这个过程可以利用滚动数组优化空间复杂度,但是在本题中并没有卡大家的空间复杂度。

以下是代码(使用滚动数组和刷表法,故写法可能跟上述描述有点不同)。

#include <bits/stdc++.h>
using namespace std;
constexpr int P = 998244353;
using i64 = long long;
// assume -P <= x < 2P
int norm(int x)
{
    if (x < 0)
     x += P;
    if (x >= P)
        x -= P;
    return x;
}
template <class T>
T power(T a, long long b)
{
    T res = 1;for (; b; b /= 2, a *= a){if (b % 2)res *= a;}return res;
}
struct Z
{
    int x;
    Z(int x = 0) : x(norm(x)) {}
    int val() const{return x;}
    Z operator-() const{return Z(norm(P - x));}
    Z inv() const{assert(x != 0);return power(*this, P - 2);}
    Z &operator*=(const Z &rhs){x = i64(x) * rhs.x % P;return *this;}
    Z &operator+=(const Z &rhs){x = norm(x + rhs.x);return *this;}
    Z &operator-=(const Z &rhs){x = norm(x - rhs.x);return *this;}
    Z &operator/=(const Z &rhs){return *this *= rhs.inv();}
    friend Z operator*(const Z &lhs, const Z &rhs){Z res = lhs;res *= rhs;return res;}
    friend Z operator+(const Z &lhs, const Z &rhs){Z res = lhs;res += rhs;return res;}
    friend Z operator-(const Z &lhs, const Z &rhs){Z res = lhs;res -= rhs;return res;}
    friend Z operator/(const Z &lhs, const Z &rhs){Z res = lhs;res /= rhs;return res;}
    friend std::istream &operator>>(std::istream &is, Z &a){i64 v;is >> v;a = Z(v);return is;}
    friend std::ostream &operator<<(std::ostream &os, const Z &a){return os << a.val();}
};
//利用结构体Z可以不写取模 实际上可以不用
const int bs = 3e4 + 2000;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, K;
    cin >> n >> K;
    vector<int> a(n + 1);
    for (int i = 0; i <= n; ++i)
        cin >> a[i];
    K -= a[0];
    vector<Z> dp(bs * 2);
    dp[bs] = 1;
    for (int i = 1; i <= n; ++i)
    {
        vector<Z> f(bs * 2);
        for (int j = 0; j < f.size(); ++j)
        {
            if (j + a[i] >= 0 && j + a[i] < bs * 2)
                f[j + a[i]] += dp[j];
            if (j - a[i] >= 0 && j - a[i] < bs * 2)
                f[j - a[i]] += dp[j];
        }
        dp = f;
    }
    cout << dp[bs + K];
    return 0;
}

动态规划的map实现代码

  • 以及验题人给定的带logmap做法(每一层都是跑不满的,最初的时限也可以过)
#include <bits/stdc++.h>

using namespace std;

constexpr int md = 998244353;

int T, n, m;
int k;

map<int, long long> dp[105];
int a[105];

int main()
{
    cin >> n >> k;
    ++ n;
    for(int i = 1; i <= n; i ++ )
        cin >> a[i];
    dp[1][a[1]] = 1;
    for(int i = 2; i <= n; i ++ )
    {
        for(auto [ans, num] : dp[i - 1])
        {
            dp[i][ans + a[i]] += num;
            dp[i][ans + a[i]] %= md;
            dp[i][ans - a[i]] += num;
            dp[i][ans - a[i]] %= md;
        }
    }
    cout << dp[n][k];
}

难题

[1] 导弹拦截特别版

出题人:周鑫

题意

给出一个长度为 n n n的数组 h h h b b b,找到一个长度为 k k k的序列 p p p,满足以下条件

  1. p 1 < p 2 < p 3 < ⋯ < p k p_{1} < p_{2} < p_{3} < \cdots < p_{k} p1<p2<p3<<pk
  2. h p 1 < h p 2 < h p 3 < ⋯ < h p k h_{p_{1}} <h_{p_{2}} <h_{p_{3}}<\cdots <h_{p_{k}} hp1<hp2<hp3<<hpk
  3. 最大化 ∑ i = 1 k b p i \sum_{i = 1}^{k} b_{p_{i}} i=1kbpi

只需输出 ∑ i = 1 n b p i \sum_{i = 1}^{n} b_{p_{i}} i=1nbpi即可。

满分做法 1 1 1

注意到 b i b_i bi的值很小,我们可以把这个问题转化一下。

对于每个导弹 我们可以把它拆成若干个数

具体为
a i × 10 + 0 , a i × 10 + 1 , a i × 10 + 2 , ⋮ a i × 10 + b i − 1 \begin{aligned} &a_{i}\times 10 +0 , \\ &a_{i}\times 10 +1 , \\ &a_{i}\times 10 +2 ,\\ &\vdots \\ &a_{i} \times 10 + b_{i} -1 \\ \end{aligned} ai×10+0,ai×10+1,ai×10+2,ai×10+bi1

按照原来顺序组合成一个新的数组 d d d

容易发现, d d d的最长上升子序列就是题目的答案。

时间复杂度为 O ( ( n + ∑ i = 1 n b i ) log ⁡ ( n + ∑ i = 1 n b i ) ) O((n + \sum_{i =1 }^{n} b_{i} )\log({n + \sum_{i =1 }^{n} b_{i} }) ) O((n+i=1nbi)log(n+i=1nbi))

参考代码

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    vector<LL> a(n), b(n);
    for (int i = 0; i < n; ++i)
        cin >> a[i];
    for (int i = 0; i < n; ++i)
        cin >> b[i];
    vector<LL> d;
    for (int i = 0; i < n; ++i)
    {
        a[i] *= 10;
        for (int j = 0; j < b[i]; ++j)
            d.push_back(a[i] + j);
    }
    vector<LL> v;
    LL ans = 0;
    for (int i = 0; i < d.size(); ++i)
    {
        if (v.empty() || v.back() < d[i])
            v.push_back(d[i]);
        else
        {
            auto it = lower_bound(v.begin(), v.end(), d[i]);
            *it = d[i];
        }
        ans = v.size();
    }
    cout << ans << endl;
    return 0;
}

满分做法 2 2 2

考虑动态规划

d p i dp_{i} dpi为从第 1 1 1个考虑到第 i i i个答案的最大值,容易写出状态转移方程

d p i = max ⁡ ( d p j + b i ) ( h i > h j ) dp_{i} = \max(dp_{j} + b_{i}) \quad (h_{i} > h_{j}) dpi=max(dpj+bi)(hi>hj)

利用离散化和线段树可以将时间复杂度优化到 O ( n log ⁡ n ) O(n\log n) O(nlogn), 这里不在赘述。

[2] Kth-Number

出题人:林贝宁

题目陈述

描述:把只包含质因子2、3和5的数称作贝贝数(Ugly Number)。例如6、8都是贝贝数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个贝贝数。求按从小到大的顺序的第N个贝贝数。

分数设置

  • set做法,但是不会用给定的类 10 / 25 10/25 10/25
    • 出题人认为本题核心是考查算法思想,使用的类是次要,故给了大头的分数。
    • 但是,出题人给了这个类的过于详细的使用方法,以及很多提醒点,如果再只拿这个档次的分数,其实有点说不过去了。
    • 再者蓝桥允许查阅给定得C++API一定程度上考查了选手现场使用、学习文档的能力
    • (原本这个档次设置的为 4 / 25 4/25 4/25分)
  • set做法,且会用给定的类,不会离线, 14 / 25 14/25 14/25
  • set做法,且会用给定的类,会离线, 18 / 25 18/25 18/25
  • 会三指针做法,会用给定得类,不会离线, 14 / 25 14/25 14/25
  • 会三指针做法,会用给定得类,会离线,代码实现的常数大, 18 / 25 18/25 18/25
  • 会三指针做法,会用给定得类,会离线,代码实现的常数小,$ 25/25$分

算法一:质因数分解(暴力)

算法实现

  • 一个很朴素的做法
  • 1 ∼ ∞ 1 \sim \infty 1每次+1,一直枚举,直到找到地N个贝贝数为止
  • 那么还有一个待解决的问题,如何判断当前数字是不是贝贝数呢?
  • 我们总结一下贝贝数的性质:只能分解为3,5,7的如干次幂相乘的数,即设第 i i i个贝贝数为 u n u_n un,则 u n = 3 x × 5 y × 7 z u_n=3^x \times 5^y \times 7^z un=3x×5y×7z
  • 那么我们只需要通过质因数分解,判断他分解3,5,7后,是否为1,如果为1,则说明没有其他的因数,否则则有其他因数,那么他就不是一个贝贝数

复杂度分析

  • 时间复杂度 O ( u n ) O(u_n) O(un),其中 u n u_n un为第n个贝贝数的大小,因为贝贝数增长非常快,非常巨大,所以这是一个很差的复杂度,预期得分 0 0 0

算法二:集合/优先队列

思路引入

  • 我们试一下能否找到相邻贝贝数之间的规律,或者贝贝数生成的规律
  • 比较遗憾的是,我们通过模拟发现,相邻的贝贝数之间并没有规律,那么这题的另一个切入点,就是生成贝贝数

思路推进

  • 我们可以发现,对于 u n u_n un,它必然是由 u i ( i ∈ [ 1 , n − 1 ] ) u_i(i\in [1,n-1]) ui(i[1,n1])乘以3或5或7生成的
  • 如果对于 i i i也有 i > 1 i>1 i>1,那么必然也有 u j , ( j ∈ [ 1 , i − 1 ] ) u_j,(j \in [1,i-1]) uj,(j[1,i1])乘以3或5或7生成 u i u_i ui
  • 所以,如果知道前面n-1个贝贝数,我们可以每个数都乘以3,5,7,然后检查出里面跟前面n-1个贝贝数不重复的并且是最小的数,得到的便是第n个贝贝数

考虑复杂度

  • 不借助set,每次检查重复的复杂度为 O ( n ) O(n) O(n),每个贝贝数生成三个新的,最多有 ( n − 1 ) (n-1) (n1)个贝贝数,时间复杂度 O ( 3 n 2 ) O(3n^2) O(3n2)
  • 如果借助set去重,每次检查重复的复杂度为 O ( log ⁡ n ) O(\log n) O(logn),时间复杂度 O ( 3 n log ⁡ n ) O(3n \log n) O(3nlogn)
  • 取出最小值,如果借助堆的话,对于维护堆,每次插入一个数,花费 O ( log ⁡ ( 3 n ) ) O(\log(3n)) O(log(3n)),最多插入3n次,每次取出最小值,花费 O ( 1 ) O(1) O(1)

实现

  • 注意,此处,进入小顶堆的元素可能会有重复,比如($ 3\times 5$和 5 × 3 5\times 3 5×3),所以我们需要去重,这一点我们可以用STL容器中的set,内嵌红黑树,begin即是最小的元素,插入和删除的代价都是 O ( log ⁡ 2 n ) O(\log 2n) O(log2n)
  • 如果每个询问都再求解一次贝贝数,会造成大量的重复计算,我们离线处理,只需要计算所有询问中最大的那个 n n n,然后其余的答案也就顺便计算出来了
  • 此处没有离线计算,复杂度为 O ( T × n ) O(T\times n) O(T×n),可以拿到 14 / 25 14/25 14/25分,如果离线的话,复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)可以拿到 18 / 25 18/25 18/25
#include <bits/stdc++.h>
using namespace std;
/*


因为担心题解冗余代码太长了,
此处填上题目给定的struct Z和BigInt


*/
int t[11];
int main() {
    int q;
	cin >> q;
    int mx = 0;
    for(int i =1; i <= q;i ++ )
    {
        cin >> t[i];
        mx = max(t[i], mx); //离线,获取最大的询问
    }
    vector<BigInt> ans;
	set<BigInt> st; //堆的作用,同时起到去重的作用
	st.insert(1);
	while (ans.size() <= mx) {
		auto ret = *st.begin();
		st.erase(st.begin()); //弹出堆中最小值
		ans.push_back(ret);//获取新的贝贝数
		st.insert(ret * 3);
		st.insert(ret * 5);
		st.insert(ret * 7);
	}  
    for(int i =1; i <= q;i ++ )
		cout << ans[t[i] - 1] << '\n';
}

算法三:三指针做法+离线

算法思路

  • 我们会发现判断是否跟前面重复这个过程,以及维护小顶堆,会花费大量时间,不妨想一想能不能省略去这个过程?
  • 我们可以发现,如果已经知道[1~i]个贝贝数,那么将 u 1 ∼ u i u_1\sim u_i u1ui每个数都会乘以3,5,7再次放入一个队列中
  • 如果当前数是由 u n = u j × 3 u_n=u_j\times 3 un=uj×3得到的,那么下一个因为乘以3而得到的贝贝数必然是由 u j + 1 × 3 u_{j+1}\times 3 uj+1×3得到的(后面的数乘以3,必然大于这个数),对于5,7同理
  • 所以我们可以利用这个单调性维护三个指针,每个指针指向队列中的一个数,依次比较三个指针所指向的数所生成的新贝贝数,即可 O ( n ) O(n) O(n)得出第n个贝贝数
  • 即维护i,j,k指针,其中i,j,k分别为指向下一个因为乘以 3 , 5 , 7 3,5,7 3,5,7而得到的贝贝数的位置,我们就可以在三个指针所对应的数的乘以相应的数的运算结果中,找到下一个贝贝数

代码实现

  • 注意,下面的if,不能写成if-else,因为可能出现v[i]*2==v[j]*3这样的情况,这种情况我们就需要同时移动i,j
  • 否则,数组v中就可能出现重复的元素,导致错误答案
  • 如果不离线处理,依旧只能拿 14 / 25 14/25 14/25分,离线处理,若常数大只能拿 18 / 25 18/25 18/25分,常数小可以拿 25 / 25 25/25 25/25满分。题目给出了可能被卡常的多数情况,可谓是非常良心了
  • (最后我还是难以理解 O ( n log ⁡ n ) O(n\log n) O(nlogn)居然被勋总给卡过去了,“真·卡常大师”,跑的比标程还快)
#include <bits/stdc++.h>
using namespace std;
/*


因为担心题解冗余代码太长了,
此处填上题目给定的struct Z和BigInt


*/
int main()
{
    int i = 0, j = 0, k = 0;
    BigInt now;       // i,j,k分别为指向下一个*3,*5,*7可能成为下一个贝贝数的数的位置的指针
    vector<BigInt> v; //放入1个1
    v.push_back(BigInt(1));
    int idx = 3e5;
    BigInt ti, tj, tk;
    while (v.size() < idx)
    {  // v中的数量为为idx时候,停止循环   
        ti = v[i] * 3;
        tj = v[j] * 5;
        tk = v[k] * 7;          
        now = min(ti, min(tj, tk)); //三个指针运算的结果中找,下一个贝贝
        v.push_back(now); //将下一个贝贝数入队
        if (ti == now)
            i++; //下一个贝贝数可以由v[i]*3得到,则i指针后移
        if (tj == now)
            j++; //下一个贝贝数可以由v[j]*5得到,则j指针后移
        if (tk == now)
            k++; //下一个贝贝数可以由v[k]*7得到,则k指针后移
        //此处不能写if -else ,因为可能存在v[i]*3==v[j]*5这种情况
        //那么在下一次循环中,v[j]*3就会被再次选中,这样就会造成v中有重复元素出现
    }
    int T, x;
    cin >> T;
    for (int t = 1; t <= T; t++)
    {
        cin >> x;
        cout << v[x - 1] << '\n';
    }
}

压轴题:Kth-Wave

出题人:林贝宁

题目陈述

大意:定义波浪形序列为:序列中间的每个数都大于他的相邻的数或者小于他相邻的数。大小定义为字典序大小,求长度为n的序列中第k个波浪型的序列。

算法一:朴素算法(暴力)

算法思路

  • 一个很显然的思路,就是暴力枚举,字典序递增算出每一个序列,直到第k个
  • 开一个vector来记录当前的序列,第i层代表当前要填写的是第i个数字,那么递归边界就是n+1层(前面n个数字都已经填写完毕)
  • 那么我们该如何按字典序搜索?对于同一个位置填写的i,下一个位置如果填写的下降的,显然比上升的字典序来的小,所以应该先搜索下降的,再搜索上升的
  • 如果确定了前两个数的关系,整个序列的山顶和山谷的位置也就确定了,只需要定义一个f_inc不断在0,1翻转就行了

代码实现

typedef long long LL;
typedef vector<int> vci;
#define pb push_back
const int N = 22;
class Solution
{
public:
	bool vis[N], findAns;
	vci ans;
	LL num;
	void dfs(int now, int last, bool f_inc, int &n, LL &k)
	{
		if (now == n + 1)//获得一个合法的序列
		{
			num++;
			if (num == k)//需找到答案
			{
				findAns = 1;
				return;
			}
			return;
		}
		if (!f_inc)
		{ //当前位置是山顶
			for (int i = last + 1; i <= n; i++)
			{
				if (!vis[i])//如果i未使用
				{
					ans.pb(i);//记录
					vis[i] = 1;//标记已经使用
					dfs(now + 1, i, f_inc ^ 1, n, k);//下一个位置跟当前位置的f_inc相反
					if (findAns)
						return;
					vis[i] = 0;
					ans.pop_back();//删除i
				}
			}
		}
		else
		{
			for (int i = 1; i < last; i++)
			{ //当前位置是山谷
				if (!vis[i])//i未被使用过
				{
					ans.pb(i);//记录
					vis[i] = 1;//标记已经使用
					dfs(now + 1, i, f_inc ^ 1, n, k);//下一个位置跟当前位置的f_inc相反
					if (findAns)
						return;
					vis[i] = 0;//还原
					ans.pop_back();//删除I
				}
			}
		}
		return;
	}
	vci stick(int n, LL k)
	{
		ans.clear();
		num=0;
		findAns=0;
		memset(vis, 0, sizeof vis);
		for (int i = 1; i <= n; i++)
		{
			ans.push_back(i);//记录
			vis[i] = 1;
			dfs(2, i, 1, n, k); //第一个位置是山顶,下一个位置是山谷(f_inc==1)
			//因为对于同样一个i,下一个位置如果越小,则字典序更小
			//所以下一个位置优先是山谷,f_inc=1
			if (findAns)//如果找到答案则返回
			{
				return ans;
			}

			dfs(2, i, 0, n, k); //第一个位置是山谷,下一个位置是山顶(f_inc==0)
			//搜索下一个位置是山顶的情况,即f_inc=0
			if (findAns)
				return ans;
			vis[i] = 0;
			ans.pop_back();//将尾巴弹出
		}
		return {};
	}
};

复杂度分析

  • 时间复杂度,对于第一个位置上面都填写的i,综合开头上升和开头下降来看,比他小的所有数,和比他大的所有数,都会被枚举一遍,对于第j个位置类似,已经选取j个数字,剩下的n-j个数字都会在第j+1个位置枚举一遍,故时间复杂度为 O ( n ! ) O(n!) O(n!)
  • 空间复杂度,定义了动态数组ans,和数组vis,为 O ( n ) O(n) O(n)

算法二:数位DP+set维护

算法思路

  • 显然上述算法还是会TLE的,所以我们仍然需要优化
  • 做题的时候,如果我们想到了上述的暴力写法并且打了出来,那么我们可以根据已有的代码,打表找规律
  • 约定:开头是递增的称为上升序列,否则称为下降序列
  • 如果在长度为3的上升序列 1 , 3 , 2 {1,3,2} 1,3,2前面加上4,那么就得到了长度为4的下降序列。
  • 我们不妨大胆猜测,长度为n-1的上升序列,是否存在着某种转换,可以变为长度为n的下降序列?
  • 下面我们思路继续推进

思路推进

打表

  • 我们可以打表(暴力或者自己写)得到以下的序列
  • 长度为4的波浪形序列
1 3 2 4 
1 4 2 3 
2 1 4 3 
2 3 1 4
2 4 1 3
3 1 4 2
3 2 4 1
3 4 1 2
4 1 3 2
4 2 3 1
  • 还有长度为5的
1 3 2 5 4 
1 4 2 5 3 
1 4 3 5 2 
1 5 2 4 3 
1 5 3 4 2 
2 1 4 3 5
2 1 5 3 4
2 3 1 5 4
2 4 1 5 3
2 4 3 5 1
2 5 1 4 3
2 5 3 4 1
3 1 4 2 5
3 1 5 2 4
3 2 4 1 5
3 2 5 1 4
3 4 1 5 2
3 4 2 5 1 
3 5 1 4 2
3 5 2 4 1
4 1 3 2 5
4 1 5 2 3
4 2 3 1 5
4 2 5 1 3
4 3 5 1 2
4 5 1 3 2
4 5 2 3 1
5 1 3 2 4
5 1 4 2 3
5 2 3 1 4
5 2 4 1 3

序列的变换——状态转移方程

  • 约定: P ( l e n , i , f ) P(len,i,f) P(len,i,f)代表长度为 l e n len len i i i开头的波浪形序列, f = 1 f=1 f=1为上升序列, f = 0 f=0 f=0为下降序列
  • 此处我先给出下降序列的状态转移方程
    d p n , i , 0 = ∑ j = 1 i − 1 d p n − 1 , j , 1 dp_{n,i,0}=\displaystyle \sum_{j=1}^{i-1} dp_{n-1,j,1} dpn,i,0=j=1i1dpn1,j,1
  • 再看我的解释,应该就更好理解 P ( n − 1 , j , 1 ) P(n-1,j,1) P(n1,j,1)如何变换到 P ( n , i , 0 ) P(n,i,0) P(n,i,0),其中 j < i j<i j<i
  • 我们假如在 P ( 4 , 1 , 1 ) P(4,1,1) P(4,1,1)前面加上一个2,如
    {1,3,2,4}-->{2,1,3,2,4}那么他是不是一个长度为5的序列?
  • 当然我们还得把原本的那个2给换成5,就变成了{2,1,3,5,4},肯定有读者想问显然这依旧不是一个波浪形序列?
  • 对的,所以还需要再变换{2,1,3,5,4}-->{2,1,5,3,4}将5和3、4中小的那个交换,这样就得到了一个下降序列
  • 肯定有同学想问,为什么要跟小的那个交换,就不能直接换成{n,最小,次小}的格式吗,我们看下面的例子
{1,3,2,4}-->{2,1,5,3,4}
{1,4,2,3}-->{2,1,4,3,5}
  • 如果我们按照上述的同学的方法来做的话,显然第二个序列和第一个序列就会映射到同一个 P ( 5 , 2 , 0 ) P(5,2,0) P(5,2,0),就不符合1对1的映射
  • 接下来我们来总结一下变换的步骤
  1. 将i放在最前面
  2. 将原本的i改为n
  3. 因为n必然是最大的数,所以要使他变为山峰,将n跟他左右中较小的数交换
  4. (如果n在最右边就跟左边那个数交换)
  • 故所以对于所有 j < i j<i j<i的数,都可以从 P ( n − 1 , j , 1 ) P(n-1,j,1) P(n1,j,1)变换到 P ( n , i , 0 ) P(n,i,0) P(n,i,0),即状态转移方程为
    d p n , i , 0 = ∑ j = 1 i − 1 d p n − 1 , j , 1 dp_{n,i,0}=\displaystyle \sum_{j=1}^{i-1} dp_{n-1,j,1} dpn,i,0=j=1i1dpn1,j,1

上升序列的DP方程

  • 此处依旧先给出dp方程
    d p n , i , 1 = ∑ j = i n − 1 d p n − 1 , j , 0 dp_{n,i,1}=\displaystyle \sum_{j=i}^{n-1} dp_{n-1,j,0} dpn,i,1=j=in1dpn1,j,0
  • 理解了下降序列的状态转移方程,现在理解上升序列的状态转移方程应该容易一些
  • 下面我们分类讨论j的情况
对于j==i的变换方式

2 1 4 3-->2 5 1 4 3,对于P(n-1,i,0),只需要在i后面添加上n,因为n必然是最大的,所以也就变成了上升序列

对于j>i,且i的原位置是山顶
  • 我们在j前面加上i,{3,4,1,2}-->{2,3,4,1,2}
  • 再把原本的2换成5,因为2原本就是山顶,故换完之后无需变换,{2,3,4,1,2}-->{2,3,4,1,5}
对于j>i,且i的原位置是山谷
  • 因为n比所有数都要打,故换完之后需要调整
  • 调整方式跟下降序列的调整方式一样
  • {3,2,4,1}-->{2,3,2,4,1}-->{2,3,5,4,1}-->{2,5,3,4,1}

故每一个 P ( n − 1 , j , 0 ) P(n-1,j,0) P(n1,j,0)都可以变换为唯一一个 P ( n , i , 1 ) P(n,i,1) P(n,i,1),其中 i ≤ j ≤ n − 1 i\le j\le n-1 ijn1,即状态转移方程为 d p n , i , 1 = ∑ j = i n − 1 d p n − 1 , j , 0 dp_{n,i,1}=\displaystyle \sum_{j=i}^{n-1} dp_{n-1,j,0} dpn,i,1=j=in1dpn1,j,0

再次推进

  • 现在我们已经得知dp方程如下:
    d p n , i , 0 = ∑ j = 1 i − 1 d p n − 1 , j , 1 dp_{n,i,0}=\displaystyle \sum_{j=1}^{i-1} dp_{n-1,j,1} dpn,i,0=j=1i1dpn1,j,1
    d p n , i , 1 = ∑ j = i n − 1 d p n − 1 , j , 0 dp_{n,i,1}=\displaystyle \sum_{j=i}^{n-1} dp_{n-1,j,0} dpn,i,1=j=in1dpn1,j,0
  • 接下来我们就是利用dp方程来求解答案了,因为是字典序递增的,且第k个序列必然存在,所以我们可以遍历找到第个字典序开头的数字是哪一个
  • 接下来我们要寻找的长度减少了1,我们也知道n-1对应的dp方程,但是已经使用过一个数了,里面的数不一定是1-n怎么办?
  • 我们可以理解成一种哈希映射,将他们排个序,依次映射到1-n,序列的个数依旧不变
  • 既能排序又能记录去掉的数,显然这个容器,set无疑

复杂度分析

  • 时间复杂度,求解dp数组为 O ( n 2 ) O(n^2) O(n2),求解答案序列的第i个数字为 O ( n ) O(n) O(n),总共有n个数字,求解答案序列总得为 O ( n 2 ) O(n^2) O(n2),故整个算法的时间复杂度为 O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度,定义了上升和、下降和为 O ( n ) O(n) O(n),定义了答案序列为 O ( n ) O(n) O(n),定义了dp数组为 O ( n 2 ) O(n^2) O(n2),总得为 O ( n 2 ) O(n^2) O(n2)

代码实现

C++

 #include <bits/stdc++.h>
using namespace std;

typedef long long LL;

int T, n, m;
typedef vector<int> vci;
const int N = 22;

LL dp[N][N][2];
//归类一下,序列可以分为有两种
// dp[n][k][0,1]代表序列长度为n位,首位是k,
//先下降(0表示)或先上升(1表示)的序列数量
LL decSum[N], incSum[N];
vci solve(int n, LL k)
{
    incSum[0] = 0;
    dp[1][1][0] = dp[1][1][1] = 1;
    //因为要考虑长度为len时,
    //对于len-1很多状态会改变,很多地方可以加入新的数len
    //所以借助辅助数组inc,incSum
    for (int len = 2; len <= n; len++)
    {
        decSum[len] = 0;
        for (int i = 1; i < len; i++)
        {
            incSum[i] = incSum[i - 1] + dp[len - 1][i][1];
            // incSum代表长度为len-1的序列中,开头为1~i的上升序列的数量的前缀和
        }
        for (int i = len - 1; i >= 0; i--)
        {
            decSum[i] = decSum[i + 1] + dp[len - 1][i][0];
            // decSum代表长度为len-1的序列中,开头为i~len-1的下降序列的数量的后缀和
        }
        for (int i = 1; i <= len; i++)
        {
            dp[len][i][0] = incSum[i - 1]; //下降序列的数量,等于1~i-1的前缀和
            dp[len][i][1] = decSum[i];     //上升序列的数量,等于k~len-1对的后缀和
        }
    }
    int last;   //记录上一个位置填写的是set中第几小的数字
    set<int> s; //记录哪些数字被用过了
    vci ans;    //储存答案
    for (int i = 1; i <= n; i++)
        s.insert(i);
    bool f_inc;
    for (int i = 1; i <= n; i++)
    {
        //此处应该先比较下降序列的,再比较上升序列的
        //顺序不能调换,字典序原因
        if (dp[n][i][0] < k)
        {                     //说明还不在范围内,此处我们也可以用一个sum累加然后和k比较
            k -= dp[n][i][0]; //继续缩小范围
        }
        else
        {
            last = i;
            ans.push_back(i); //放入答案中
            f_inc = 1;        //下一个位置是山顶
            s.erase(i);       //从维护的set中删除i,表示已经被用过了
            break;
        }
        if (dp[n][i][1] < k)
        {
            k -= dp[n][i][1];
        }
        else
        {
            last = i;
            ans.push_back(i); //放入答案中
            f_inc = 0;        //下一个位置是山谷
            s.erase(i);       //从维护的set中删除i,表示已经被用过了
            break;
        }
    }
    int idx;
    //上升代表当前位置是山谷,下降代表当前位置是山顶
    //长度逐渐减小的时候,dp数组中代表的1-n就可以理解成为离散化后的结果
    //可以理解成为哈希映射后的结果
    for (int len = n - 1; len >= 1; len--)
    {
        if (f_inc)
            idx = 1; //如果当前位置是山谷,则从1开始枚举
        //实际枚举区间为[1,last],但是因为必然存在,故i到达len之前就已经break
        else
            idx = last; //如果当前位置是山顶,则从last开始枚举
        //实际枚举区间为[last,len]
        //之前的last已经被删除了
        for (int j = idx; j <= len; j++)
        {
            if (dp[len][j][f_inc] < k)
                k -= dp[len][j][f_inc]; //继续缩小范围,分而治之
            else
            {
                auto it = s.begin();
                for (int q = 1; q < j; q++)
                    it++; //因为迭代器不能直接+(j-1),故找set中第j小的数字得一步一步找
                last = j; //对于下次来说,上次找的是第j小的数
                ans.push_back(*it);
                s.erase(it);
                break;
            }
        }
        f_inc ^= 1; //下一个位置,跟当前位置相反
    }
    return ans;
}

int main()
{
    int n;
    LL k;
    cin >> n >> k;
    vci ans = solve(n, k);
    for (int i = 0; i < ans.size(); i++)
    {
        if (i > 0)
            cout << ' ';
        cout << ans[i];
    }
}

python

此处写了实现类,需要选手自己调用

class Solution:
    def stick(self, n, k):
        inc = []
        ans = []
        now = 0
        dec =[[0 for i in range(n + 1)] for j in range(n + 1)]#python不能用连环等号
        #如果此处用连续等号,后面的dp数组会有问题
        inc = [[0 for i in range(n + 1)] for j in range(n + 1)]  # n+1个n+1个0,二维数组
        inc[1][1] = 1
        dec[1][1] = 1  # 初始长度为1
        vis = [0 for i in range(n + 1)]
        for Len in range(2, n + 1):  # 长度从2到n

            for i in range(1, Len + 1):  # 开头的数字从1到Len
                for m in range(1, i):  # 下降由上升1-(i-1)的和转移过来
                    dec[Len][i] += inc[Len - 1][m]
                for m in range(i, Len):  # 上升由下降i-(Len-1)的和转移过来
                    inc[Len][i] += dec[Len - 1][m]
        for i in range(1, n + 1):  # i从1到n,当前需要枚举的位置
            m = 0
            now = 0
            Len =n-i+1#剩余需要枚举的长度
            for j in range(1, n + 1):
                if (not vis[j]):  # 没有访问过
                    m += 1#相当于C++中的set来储存
                    #第i层计算ans中的第i个数,下标为ans[i-1]
                    if i == 1:
                        now = inc[Len][m] + dec[Len][m]
                    elif (j > ans[i - 2] and (i == 2 or ans[i - 2] < ans[i - 3])):#当前位置是山顶,即开头递减
                        now=dec[Len][m]
                    elif (j<ans[i-2] and (i==2 or ans[i-2]>ans[i-3])):#当前位置是山谷,即开头是递增
                        now=inc[Len][m]

                    if k<=now:
                        vis[j]=1#这个数被使用过了
                        ans.append(j)#放入答案
                        break
                    else:
                        k-=now
        return ans

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

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

相关文章

PLC程序实例二:ModBusTCP客户端编程实例与测试方法

一、需求描述 1、设备作为服务端时&#xff0c;需要给出对应的测试方法&#xff0c;即要求 PLC 作为客户端&#xff0c;设备作为服务端&#xff0c;因此要求编写 PLC 的ModBusTCP客户端 2、先了解一下设备作为服务端的ModBusTCP网络触发业务逻辑 &#xff08;1&#xff09;设…

SQL 语法速成手册

基本概念 数据库术语 数据库&#xff08;database&#xff09;&#xff1a;保存有组织的数据的容器&#xff08;通常是一个文件或一组文件&#xff09;。数据表&#xff08;table&#xff09; &#xff1a;某种特定类型数据的结构化清单。模式&#xff08;schema&#xff09;…

JAVA SCRIPT设计模式--创建型设计模式之抽象工厂(1)

JAVA SCRIPT设计模式是本人根据GOF的设计模式写的博客记录。使用JAVA SCRIPT语言来实现主体功能&#xff0c;所以不可能像C&#xff0c;JAVA等面向对象语言一样严谨&#xff0c;大部分程序都附上了JAVA SCRIPT代码&#xff0c;代码只是实现了设计模式的主体功能&#xff0c;不代…

这俩个技巧 解决了90%的冲突

解决冲突的关键技巧 关于本书作者&#xff1a; 达纳.卡斯帕森&#xff0c;冲突调节的专家&#xff0c;尚普兰大学人际冲突专业的硕士。三次获得芭蕾舞国际协会搬的最佳舞者奖。 关于本书&#xff1a; 这是一本引导别人如何化解一段冲突&#xff0c;达成一次有效沟通的社交类…

DataX 及 DataX-Web 安装使用详解

文章目录一、DataX安装1、解压datax.tar.gz2、运行自检脚本二、Datax-Web安装1、解压DataXWeb安装包2、执行一键安装脚本3、启动服务前段时间在项目上使用了阿里的离线数据同步工具datax&#xff0c;在大批量的表同步过程中踩了一些坑&#xff0c;所以详细介绍一下&#xff0c;…

五、伊森商城 前端基础-Vue Vue脚手架原理与使用 p27

目录 Vue项目结构 一、使用vue脚手架进行模块化开发 1、main文件 1.1、首先new Vue创建了一个vue实例&#xff0c;这个实例挂载了index.html中的app元素 1.2、使用了路由&#xff0c;这个路由是简写的写法 1.3、components使用了一个组件叫App 1.4、最终渲染 2、App.vu…

卷积神经网络相关模型

卷积神经网络相关模型1. CNN架构2. CNN架构模型2.1LeNet模型2.2Alexnet模型2.3VGG16模型2.3.1 使用多层小卷积核代替一层大的卷积核优点2.3.2 使用1*1卷积核优点2.3.2 模型特点卷积神经网络基本概念相关知识连接 卷积神经网络基本概念相关知识连接 1. CNN架构 模型分为2部分【…

ubuntu1804在rviz中显示kitti数据集的2D检测框和激光雷达的3D检测框

之前所做的工作参考上篇博客ubuntu1804发布kitti数据集的gps资料,imu资料(包含发布图片,点云过程)_FYY2LHH的博客-CSDN博客 本次将详解如何在ros播放的kitti数据集上作出检测框,首先需要知道的是对于kitti数据集中的每一帧都必须提前进行标注,而这个工作已经被别人完成了…

42. Python range函数—生成器函数

42. range函数—生成器函数 文章目录42. range函数—生成器函数1. 什么是range( )函数2. 回顾列表切片的语法3. range( )函数的语法3. range函数实操3.1 只有1个参数3.2 有2个参数3.3 有3个参数3.4 步长为负数4. list不能完全替代range5. 总结1. 什么是range( )函数 range[reɪ…

基于遗传算法在机器人路径规划中的应用研究(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

第十七章 优先队列优化Dijkstra算法

第十七章 优先队列优化Dijkstra算法一、普通dijkstra算法的缺陷1、选出最小距离的过程&#xff1a;2、松弛所有点的过程&#xff1a;二、如何优化1、代码模板&#xff08;1&#xff09;问题&#xff1a;&#xff08;2&#xff09;模板&#xff1a;2、详细解读三、优化分析1、使…

5G赋能行业应用,助推数智化转型!

导语 | 5G 技术的蓬勃发展推动其行业应用的巨轮乘风破浪、扬帆启航&#xff0c;5G 技术不断落地布局于各行各业&#xff0c;基础建设也不断完善&#xff0c;5G to B 的时代已经到来。此次&#xff0c;我们邀请到了中国电信研究院工业互联网技术研发部主任、腾讯云 TVP 谭华老师…

知识图谱-KGE-语义匹配-双线性模型(打分函数用到了双线性函数)-2014:TATEC

【paper】 Effective Blending of Two and Three-way Interactions for Modeling Multi-relational Data 【简介】 本文是法国 Antoine Bordes 团队发表在 ECML-PKDD 2014 上的工作&#xff0c;提出了 TATEC&#xff08;Two and Three-way Embeddings Combination&#xff09;…

ERP+MES集成管理系统重要性有哪些?

随着企业信息化观念的提升&#xff0c;管理方式也愈来愈信息化&#xff0c;因此以信息化推动企业的不断发展趋势已变成企业存活和发展的主要核心理念。其中E&#xff32;P 系统和 MES 系统在企业发展和改革中起着非常重要的作用。当各种信息化系统在企业内各个部门顺利执行的同…

RabbitMQ安装教程(超详细)

本教程是在centos8下试验的&#xff0c;其实linux系统的都差不多 RabbitMQ官方&#xff1a;Messaging that just works — RabbitMQ RabbitMQ是开源AMQP实现&#xff0c;服务器端用Erlang语言编写&#xff0c;Python、Ruby、 NET、Java、JMS、c、PHP、action screcrive AMQP …

python 爬虫入门该怎么学习?

前言 1、什么是爬虫和爬虫的基本逻辑 “爬虫”是一种形象的说法。互联网比喻成一张大网&#xff0c;爬虫是一个程序或脚本在这种大网上爬走。碰到虫子&#xff08;资源&#xff09;&#xff0c;若是所需的资源就获取或下载下来。这个资源通常是网页、文件等等。可以通过该资源…

Android NDK 开发之 CMake 必知必会

Android Studio 从 2.2 版本起开始支持 CMake ,可以通过 CMake 和 NDK 将 C/C 代码编译成底层的库&#xff0c;然后再配合 Gradle 的编译将库打包到 APK 中。 这意味就不需要再编写 .mk 文件来编译 so 动态库了。 CMake 是一个跨平台构建系统&#xff0c;在 Android Studio 引…

12月7日 补充数据库链式操作,只生成sql语句,不执行sql的几种函数以及thinkphp中验证器

补充知识&#xff1a; 生成sql语句但是不会执行sql语句 public function fetchSqlTest(){$result Db::name(user)->fetchSql(true)->select();//dump($result);//使用正常select查询默认中select(true&#xff09;改为false即可返回sql语句$result Db::name(user)->…

UNIX/LINUX操作系统内核结构

通用操作系统的现状和分类 UNIX操作系统的根本特点&#xff1a;分时多用户、开放性 基本功能特征 系统结构 操作系统核心 系统调用的集合及实现系统调用的内部算法就形成操作系统核心 系统调用 以函数形式提供给核外的命令和上层应用系统使用的一组程序&#xff0c;涵…

观察者模式

目录 一、观察者模式解决了哪些问题&#xff1a; 二、体检的小栗子 三、什么是观察者模式 四、代码实现 一、观察者模式解决了哪些问题&#xff1a; 对象之间的一对多依赖关系应该在不使对象紧密耦合的情况下定义。应该确保当一个对象改变状态时&#xff0c;自动更新无限数…