第一题:打游戏
在线测评链接:http://121.196.235.151/p/P1145
题目描述
ak机正在玩一个游戏,游戏中
n
n
n个怪物,血量上限分别为
h
i
h_i
hi,初始时所有怪物的血量都等于它们的血量上限,当怪物的血量小于或等于0时,怪物将会死亡。
ak机有两个技能
第一个技能为旋风斩,消耗一点法力,对所有怪物造成1点伤害
第二个技能为斩杀,消耗两点法力,杀死一个已受伤的怪物(当前怪物血量小于怪物的血量上限)。
两个技能都没有使用次数限制,ak机想知道她最少需要消耗多少点法力才能杀死所有怪物。
输入描述
第一行输入一个整数 n ( 2 ≤ n ≤ 1 0 5 ) n(2 ≤n≤ 10^5) n(2≤n≤105)表示怪物数量。
第二行输入 n n n个整数 h i ( 1 ≤ h i ≤ 1 0 9 ) h_i(1 ≤ h_i≤ 10^9) hi(1≤hi≤109)表示怪物的血量上限
输出描述
输出一个整数表示答案,
样例
输入
3
1 1 4
输出
3
说明
首先使用旋风斩,怪物的血量变成:0 0 3,第1、2个怪物死亡。
再对第三个怪物使用斩杀,第3个怪物死亡。消耗的法力值为1+2=3。
思路:模拟 枚举
首先,根据题目描述,一技能一定要至少释放一次(这是释放二技能的前提)
首先一种暴力的思想是可以直接枚举使用一技能的次数 c n t cnt cnt
但是,由于怪物的血量范围是 1 ≤ h i ≤ 1 0 9 1\le h_i\le 10^9 1≤hi≤109,因此我们肯定不能直接枚举 c n t cnt cnt
由于只有 n ( 1 ≤ n ≤ 1 0 5 ) n(1\le n\le 10^5) n(1≤n≤105)个怪物,因此我们可以对怪物的血量从小到大进行排序,然后枚举第 i i i个怪物被一技能直接干掉
第 i i i个怪物被一技能直接干掉的一技能使用次数即为 w [ i ] w[i] w[i],显然前面 i − 1 i-1 i−1个怪物也会被干掉
对于后面的怪物,都是用第二个技能,即为第 i i i个怪物的最小花费
因此第 i i i个怪物的最小花费即为 w [ i ] + ( n − i − 1 ) × 2 w[i]+(n-i-1)\times 2 w[i]+(n−i−1)×2
C++
#include<bits/stdc++.h>
using namespace std;
const int N=1E5+10;
int w[N],n;
int main(){
cin>>n;
for(int i=0;i<n;i++)cin>>w[i];
sort(w,w+n);
int res=1e9;
for(int i=0;i<n;i++){
res=min(res,w[i]+(n-i-1)*2);
}
cout<<res<<endl;
return 0;
}
Java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
Integer[] w = new Integer[n];
for(int i = 0; i < n; i++) {
w[i] = scanner.nextInt();
}
Arrays.sort(w);
int res = (int)1e9;
for(int i = 0; i < n; i++) {
res = Math.min(res, w[i] + (n - i - 1) * 2);
}
System.out.println(res);
}
}
Python
n = int(input())
w = list(map(int, input().split()))
w.sort()
res = int(1e9)
for i in range(n):
res = min(res, w[i] + (n - i - 1) * 2)
print(res)
第二题:互补数组
在线测评链接:http://121.196.235.151/p/P1146
题目描述
ak机定义两个数组是互补的,当且仅当数组每一个位置的数字之和都相同。
ak机有两个长度为 n n n的数组,分别是 a a a和 b b b,她想知道有多少个子序列对应的数组是互补的。
输入描述
第一行输入一个整数 n ( 1 ≤ n ≤ 1 0 5 ) n(1 ≤n≤ 10^5) n(1≤n≤105)表示数组长度
第二行输入 n n n个整数表示数组 a ( 1 ≤ a i ≤ 1 0 9 ) a(1≤ a_i≤ 10^9) a(1≤ai≤109)
第三行输入 m m m个整数表示数组 b ( 1 ≤ b i ≤ 1 0 9 ) b(1 ≤b_i\le 10^9) b(1≤bi≤109)。
输出描述
输出一个整数,由于这个整数可能很大,因此你需要输出这个整数对 1 0 9 + 7 10^9 +7 109+7取模后的结果。
样例
输入
3
1 2 3
3 2 1
输出
7
说明
子序列:1,2,3,12,13,23,123,都满足条件
这里的1,2,3指的是数组下标的位置(从1开始)
思路:哈希表+贡献法计数+快速幂
首先解释一下样例1,因为任意一个位置 i i i, a [ i ] + b [ i ] a[i]+b[i] a[i]+b[i]都为4,因此有3个4,我们可以考虑选或者不选(至少要选1个位置)
因此,总的情况就是 2 3 − 1 = 7 2^3-1=7 23−1=7
我们可以使用一个哈希表来统计所有位置 a [ i ] + b [ i ] a[i]+b[i] a[i]+b[i]出现的次数,对于某一个权值 w w w,其出现的次数为 c n t cnt cnt
那么它对答案的贡献就是 2 c n t − 1 2^{cnt}-1 2cnt−1
注意由于答案过大,需要对 1 0 9 + 7 10^9+7 109+7取模,因此这里我们可以使用快速幂来快速地求出来 2 k 2^k 2k,更新答案
本题数据范围较小,因此这里就不给大家使用快速幂的做法了,我们使用另一种做法,维护一个数组来预处理 2 k 2^k 2k的结果
C++
#include<bits/stdc++.h>
using namespace std;
const int N=1E5+10,mod=1e9+7;
int a[N],b[N],n,res;
int main(){
cin>>n;
for(int i=0;i<n;i++)cin>>a[i];
for(int i=0;i<n;i++)cin>>b[i];
unordered_map<int,int>cnts;
for(int i=0;i<n;i++){
cnts[a[i]+b[i]]++;
}
vector<int>twos(n+1,1);
for(int i=1;i<=n;i++){
twos[i]=1ll*twos[i-1]*2%mod;
}
for(auto &[u,v]:cnts){
res=(res+1ll*(twos[v]-1+mod)%mod)%mod;
}
cout<<res<<endl;
return 0;
}
Java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] a = new int[n];
int[] b = new int[n];
for(int i = 0; i < n; i++) {
a[i] = scanner.nextInt();
}
for(int i = 0; i < n; i++) {
b[i] = scanner.nextInt();
}
HashMap<Integer, Integer> cnts = new HashMap<>();
for(int i = 0; i < n; i++) {
cnts.put(a[i] + b[i], cnts.getOrDefault(a[i] + b[i], 0) + 1);
}
int[] twos = new int[n + 1];
twos[0] = 1;
int mod = (int)1e9 + 7;
for(int i = 1; i <= n; i++) {
twos[i] = (int)(((long)twos[i - 1] * 2) % mod);
}
int res = 0;
for(int v : cnts.values()) {
res = (res + ((twos[v] - 1 + mod) % mod)) % mod;
}
System.out.println(res);
}
}
Python
n = int(input())
a = list(map(int, input().split()))
b = list(map(int, input().split()))
cnts = {}
for i in range(n):
cnts[a[i] + b[i]] = cnts.get(a[i] + b[i], 0) + 1
twos = [1] * (n + 1)
mod = int(1e9) + 7
for i in range(1, n + 1):
twos[i] = twos[i - 1] * 2 % mod
res = 0
for v in cnts.values():
res = (res + (twos[v] - 1 + mod) % mod) % mod
print(res)
第三题:数的最大权值
在线测评链接:http://121.196.235.151/p/P1107
题目描述
小苯定义一个数字的权值为:该数字的因子个数。
小苯现在拿到了一个正整数,他希望将:分解为若干不等于1的数字(也可以不做分解),使得所有分解出的正整数乘积等于 x x x,且所有数字的权值之和尽可能大,你能帮帮他求出最大的权值吗。
输入描述
输入包含 T 十 1 T 十1 T十1行。
第一行一个正整数 T ( 1 ≤ T ≤ 1 0 4 ) T(1\le T\le 10^4) T(1≤T≤104),表示数据组数。接下来 T T T行,每行一个正整数 x ( 2 ≤ x ≤ 1 0 5 ) x(2 \le x \le10^5) x(2≤x≤105),表示每组数据中小苯询问的数字 x x x。
输出描述
输出包含 T T T 行,每行一个正整数表示每组测试数据的最大权值和
样例
输入
3
2
10
123
输出
2
4
4
说明
第一个测试数据中,无法分解,直接取2的权值为 2.
第二个测试数据中,将 10 分解为 2x5,权值和为 4。
思路:质因数分解+分类讨论
本题有两种情况
- 情况1: x x x仅有一个质因子 a a a,即 x = a k x=a^k x=ak
考虑两种情况,拆分和不拆分
拆分:对于每一个质因子,它的因子个数都是2(1和它本身),因此对应的答案为 2 k 2k 2k
不拆分:对应因子个数为 k + 1 k+1 k+1(有 1 , a , a 2 , . . . , a k 1,a,a^2,...,a^k 1,a,a2,...,ak这些因子,共计 k + 1 k+1 k+1个)
只要 k ≥ 1 k\ge 1 k≥1,一定有 2 k ≥ k + 1 2k\ge k+1 2k≥k+1,因此拆分是最优解
例如8,只有一个质因子2,不拆分的话就只有1 2 4 8这4个因子,拆了就是3个2,共计6个因子,因此拆分比不拆分的因子个数要多。
- 情况2: x x x有多个质因子,即 x = a 1 k 1 × a 2 k 2 × . . . a m k m x=a_1^{k_1}\times a_2^{k_2}\times ...a_m^{k_m} x=a1k1×a2k2×...amkm
拆分:根据情况1的计算公式,权值总和为 ∑ i = 1 m ( k i + 1 ) \sum_{i=1}^{m}(k_i+1) ∑i=1m(ki+1)
不拆分:根据情况1的计算公式,权值总和为 ∏ i = 1 m ( y i + 1 ) \prod _{i=1}^{m}(y_i+1) ∏i=1m(yi+1)
我们可以明显得知,当 a , b ≥ 2 时 a,b\ge 2时 a,b≥2时, a × b ≥ a + b a\times b\ge a+b a×b≥a+b
因此选择不拆分是最优解
例如30,有质因子2,3,5,如果不拆分的话有1,2,3,5,6,10,15,30这8个因子,如果拆分的话,就是1个2,1个3,1个5这6个因子,因此不拆分要比拆分的因子个数要多。
C++
#include<bits/stdc++.h>
using namespace std;
const int N=1E5+10,mod=1e9+7;
int T,n;
long long solve(int x){
vector<pair<int,int>>v; //记录质因子和其出现的次数
for(int i=2;i*i<=x;i++){
if(x%i==0){
int cnt=0;
while(x%i==0){
cnt++;
x/=i;
}
v.push_back({i,cnt});
}
}
if(x>1){
v.push_back({x,1});
}
if(v.size()==1)return v[0].second*2; //只有一个质因子
long long res=1;
for(auto &t:v){
res*=(t.second+1);
}
return res;
}
int main(){
cin>>T;
while(T--){
cin>>n;
cout<<solve(n)<<endl;
}
return 0;
}
Java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int T = scanner.nextInt();
while(T-- > 0) {
int n = scanner.nextInt();
System.out.println(solve(n));
}
}
private static long solve(int x) {
List<Pair<Integer, Integer>> v = new ArrayList<>(); // 记录质因子和其出现的次数
for(int i = 2; i * i <= x; i++) {
if(x % i == 0) {
int cnt = 0;
while(x % i == 0) {
cnt++;
x /= i;
}
v.add(new Pair<>(i, cnt));
}
}
if(x > 1) {
v.add(new Pair<>(x, 1));
}
if(v.size() == 1) return v.get(0).second * 2; // 只有一个质因子
long res = 1;
for(Pair<Integer, Integer> t : v) {
res *= (t.second + 1);
}
return res;
}
static class Pair<T, U> {
T first;
U second;
Pair(T first, U second) {
this.first = first;
this.second = second;
}
}
}
Python
T = int(input())
def solve(x):
v = [] # 记录质因子和其出现的次数
i = 2
while i * i <= x:
if x % i:
i += 1
else:
cnt = 0
while x % i == 0:
cnt += 1
x //= i
v.append((i, cnt))
if x > 1:
v.append((x, 1))
if len(v) == 1: return v[0][1] * 2 # 只有一个质因子
res = 1
for t in v:
res *= (t[1] + 1)
return res
for _ in range(T):
n = int(input())
print(solve(n))