#P5000. 第1题-虚拟货币挖矿算力匹配
-
1000ms
Tried: 329
Accepted: 50
Difficulty: 7
所属公司 :
华为
时间 :2025年5月21日-暑期实习(留学生)
-
算法标签>贪心算法
第1题-虚拟货币挖矿算力匹配
解题思路
算法概述
- 转成字符串处理
将输入的正整数 n 转为长度为 L 的字符串s。 - 贪心+回溯构造
从最高位到最低位逐位决定数字。对第i位,我们在合法范围内(若受上界约束则不超过s[i],否则不超过 9;且不小于前一位已定的数字)的候选数字中,从大到小尝试最优选择。 - 可行性剪枝
每选一个数字d之后,根据当前已选的数字和剩余位置,可算出可能的最小/最大剩余位数和:
如果区间 [lo, hi] 中不存在质数,则跳过该新和 newSum = sumSoFar + d 剩余位数 rem = L−i−1 最小可能总和 lo = newSum + d*rem 最大可能总和 hi = newSum + 9*remd。 - 多长度尝试
若在与 n 同长度下无法找到解,则依次尝试长度 L−1,L−2,…,1,每次将上界字符串设为全 ‘9’。
质数及前缀和预处理
- 最大可能的数位和为 9×18=162,预先用线性筛算出
isPrime[0..162],并构造前缀和数组primeCnt[i]表示小于等于i的质数个数。 - 区间 [lo, hi] 存在质数 当且仅当
primeCnt[hi] − primeCnt[lo−1] > 0
复杂度分析
- 数字长度 L≤18。
- 每一位最多枚举 10 个候选数字,并做 O(1) 的区间质数查询。
- 回溯最坏情况也在 O(L×10) 量级。
- 最多尝试 L 种不同长度,故总体复杂度 O(L2×10),常数非常小,可视为 O(1) 级别。
代码实现
Python
import sys
def main():
s = sys.stdin.readline().strip()
L = len(s)
# 预处理质数和前缀和
MAXS = 9 * 18
is_prime = [False, False] + [True] * (MAXS - 1)
for i in range(2, int(MAXS**0.5) + 1):
if is_prime[i]:
for j in range(i*i, MAXS+1, i):
is_prime[j] = False
prime_cnt = [0] * (MAXS + 1)
cnt = 0
for i in range(MAXS + 1):
if is_prime[i]:
cnt += 1
prime_cnt[i] = cnt
# 在上界 bound_str 下搜索最大解,找不到返回空串
def search(bound_str):
m = len(bound_str)
bd = list(map(int, bound_str))
res = [0] * m
def dfs(pos, prev, tight, ssum):
if pos == m:
return is_prime[ssum]
max_d = bd[pos] if tight else 9
for d in range(max_d, prev - 1, -1):
new_sum = ssum + d
rem = m - pos - 1
lo = new_sum + d * rem
hi = new_sum + 9 * rem
# 区间 [lo, hi] 必须包含质数
if lo <= hi and prime_cnt[hi] - (prime_cnt[lo-1] if lo > 0 else 0) == 0:
continue
res[pos] = d
if dfs(pos+1, d, tight and d == max_d, new_sum):
return True
return False
return ''.join(map(str, res)) if dfs(0, 1, True, 0) else ''
# 主流程:先试原长度,再依次尝试全9的更短长度
ans = ''
for k in range(L, 0, -1):
bound = s if k == L else '9' * k
ans = search(bound)
if ans:
print(ans)
return
print(-1)
if __name__ == '__main__':
main()
Java
import java.io.*;
import java.util.*;
public class Main {
static boolean[] isPrime;
static int[] primeCnt;
static int[] bd, res;
static int m;
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = br.readLine().trim();
int L = s.length();
// 预处理
int MAXS = 9 * 18;
isPrime = new boolean[MAXS + 1];
Arrays.fill(isPrime, true);
isPrime[0] = isPrime[1] = false;
for (int i = 2; i * i <= MAXS; i++) {
if (isPrime[i]) {
for (int j = i * i; j <= MAXS; j += i) {
isPrime[j] = false;
}
}
}
primeCnt = new int[MAXS + 1];
int cnt = 0;
for (int i = 0; i <= MAXS; i++) {
if (isPrime[i]) cnt++;
primeCnt[i] = cnt;
}
// 先试原长度,再试全9的更短长度
for (int k = L; k >= 1; k--) {
String bound = (k == L ? s : String.join("", Collections.nCopies(k, "9")));
String ans = search(bound);
if (!ans.isEmpty()) {
System.out.println(ans);
return;
}
}
System.out.println(-1);
}
// 在字符串 bound 下搜索
static String search(String bound) {
m = bound.length();
bd = new int[m];
res = new int[m];
for (int i = 0; i < m; i++) bd[i] = bound.charAt(i) - '0';
return dfs(0, 1, true, 0) ? build() : "";
}
// 回溯尝试
static boolean dfs(int pos, int prev, boolean tight, int sum) {
if (pos == m) return isPrime[sum];
int maxD = tight ? bd[pos] : 9;
for (int d = maxD; d >= prev; d--) {
int newSum = sum + d;
int rem = m - pos - 1;
int lo = newSum + d * rem;
int hi = newSum + 9 * rem;
if (lo <= hi && primeCnt[hi] - (lo > 0 ? primeCnt[lo - 1] : 0) == 0)
continue;
res[pos] = d;
if (dfs(pos + 1, d, tight && d == maxD, newSum))
return true;
}
return false;
}
static String build() {
StringBuilder sb = new StringBuilder();
for (int d : res) sb.append(d);
return sb.toString();
}
}
C++
#include <bits/stdc++.h>
using namespace std;
const int MAXS = 9 * 18;
bool isPrime[MAXS+1];
int primeCnt[MAXS+1];
string boundStr;
vector<int> bd, res;
int m;
bool dfs(int pos, int prev, bool tight, int sum) {
if (pos == m) return isPrime[sum];
int maxD = tight ? bd[pos] : 9;
for (int d = maxD; d >= prev; d--) {
int newSum = sum + d;
int rem = m - pos - 1;
int lo = newSum + d * rem;
int hi = newSum + 9 * rem;
int cnt = primeCnt[min(hi, MAXS)] - (lo > 0 ? primeCnt[lo-1] : 0);
if (lo <= hi && cnt == 0) continue;
res[pos] = d;
if (dfs(pos+1, d, tight && d==maxD, newSum)) return true;
}
return false;
}
string searchBound(const string &s) {
boundStr = s;
m = s.size();
bd.assign(m, 0);
res.assign(m, 0);
for (int i = 0; i < m; i++) bd[i] = s[i] - '0';
if (!dfs(0, 1, true, 0)) return "";
string ans;
for (int d : res) ans += char('0' + d);
return ans;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
string s;
if (!(cin >> s)) return 0;
// 预处理质数和前缀和
fill(isPrime, isPrime+MAXS+1, true);
isPrime[0] = isPrime[1] = false;
for (int i = 2; i*i <= MAXS; i++) {
if (isPrime[i]) {
for (int j = i*i; j <= MAXS; j += i)
isPrime[j] = false;
}
}
int cnt = 0;
for (int i = 0; i <= MAXS; i++) {
if (isPrime[i]) cnt++;
primeCnt[i] = cnt;
}
int L = s.size();
for (int k = L; k >= 1; k--) {
string bound = (k==L ? s : string(k, '9'));
string ans = searchBound(bound);
if (!ans.empty()) {
cout << ans;
return 0;
}
}
cout << -1;
return 0;
}
题目内容
在一个虚拟货币挖矿系统中,每个矿工拥有一定的算力值n(范围在1 到1018之间)。系统需要为每个矿工分配一个算力档位,这个档位必须是小于等于矿工当前算力n的最大“稳定算力档”,并且这个档位的算力值各个数位之和必须是一个质数(质数又称素数。一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数)。“稳定算力档”定义为从左到右每一位数字都不小于前一位数字,例如123、111、399都是符合要求的稳定算力档,像121、897
这种则不符合要求。合理分配算力档位有助于提高挖矿效率和稳定性。
输入描述
给定一个正整数n(1≤n≤1018)
输出描述
返回小于等于n的最大稳定算力档,且该整数的所有数位之和为质数。如果不存在这样的整数,则返回−1
样例1
输入
111
输出
111
说明
111本身即是"稳定算力档",从左到右每一位数字都不小于前一位数字,1+1+1=3是质数,所以函数返回111
样例2
输入
1
输出
-1
说明
小于等于1的"稳定算力档"只有1,1的数位之和为1,1不是质数(质数定义为大于1的自然数中,除了1和它自身外,不能被其他自然数整除的数),所以函数返回−1
样例3
输入
123
输出
122
说明
首先小于等于123的“稳定算力档"有 123、122、111等。123 的数位之和为1+2+3=6,不是质数,不符合条件。再继续找是122,数位之和为1+2+2=5是质数符合"稳定算力档条件。111的数位之和1+1+1=3也为质数也满足"稳定算力档"的条件,但111小于122,所以小于等于n的最大稳定算力档为122,函数返回122。