第一题:英语单词
在线测评链接:http://121.196.235.151/p/P1091
题目描述
ak机读小学了,学校里新开了英语课。
某一天老师进行了一对一的口语考试,考试内容为复述老师说的话。考试要求每个人一共进行 t t t次测试,每次测试中,老师会说一句话,包含 n n n个单词,ak机每复述出一个单词,就能够获得一分,但是当分数低于0时,本次测试就会结束,并且该次测试未通过。
ak机英语很差,因此他想知道自己一共能通过多少次测试。
输入描述
第一行输入一个正整数 t t t,代表ak机需要进行的测试数量
接下来的 3 × t 3\times t 3×t行,每3行用于描述一次测试:
第一行输入一个正整数 n n n,代表老师说的一句话包含的单词数量。
第二行输入 n n n个仅由小写字母组成的字符串,用空格隔开。代表老师说的单词。
第三行输入 n n n个仅由小写字母组成的字符串,用空格隔开。代表ak机复述的话。
1 ≤ t ≤ 10 , 1 ≤ n ≤ 100 1\le t \le 10,1\le n \le 100 1≤t≤10,1≤n≤100,单词的长度均不超过10。
输出描述
一个整数,代表ak机最终通过了多少次测试。
样例
输入
3
2
hello hello
hello hello
3
how are you
hwo are you
4
how old are you
how old are yuo
输出
2
题解:模拟
维护一个变量score
和cnt
表示ak机每次答题的分数和能通过多少次测试,如果某一轮测试中某一时刻的score
的值<0,则直接跳出当前循环,如果最终有
s
c
o
r
e
≥
0
score\ge 0
score≥0,则cnt
计数+1。
C++
#include <bits/stdc++.h>
using namespace std;
const int N=2E5+10,mod=1e9+7;
int n,m;
string w[N],t[N];
int main(){
cin>>n;
int cnt=0;
for(int i=0;i<n;i++){
cin>>m;
for(int i=0;i<m;i++){
cin>>w[i];
}
for(int i=0;i<m;i++){
cin>>t[i];
}
int score=0;
for(int i=0;i<m;i++){
if(w[i]==t[i])score++;
else score--;
if(score<0){
break;
}
}
if(score>=0){
cnt++;
}
}
cout<<cnt<<endl;
return 0;
}
Java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int res = n;
while (n -- > 0) {
int len = sc.nextInt();
sc.nextLine();
String[] s1 = sc.nextLine().split(" ");
String[] s2 = sc.nextLine().split(" ");
int count = 0;
for (int i = 0; i < len; i++) {
if (s1[i].equals(s2[i])) {
count++;
} else {
count--;
}
if (count < 0) {
res--;
break;
}
}
}
System.out.println(res);
}
}
Python
t = eval(input())
final = 0
for _ in range(t):
num = int(input())
s1 = input().split()
s2 = input().split()
score = 0
for j in range(num):
if s1[j] == s2[j]:
score +=1
else:
score -=1
if score < 0:
break
if score >= 0:
final += 1
print(final)
第二题:最小攻击次数
在线测评链接:http://121.196.235.151/p/P1092
题目描述
ak机最近沉迷于一款割草游戏。所谓割草游戏,并不是割草模拟器,而是指一款击杀敌人快,很容易一次性就击杀大量敌人的游戏。
ak机每放一个大范围aoe技能下去,就能看见屏幕上一大片的敌人消失,非常解压。
这天,ak机又在玩这款割草游戏了,他看着面前的 n n n个敌人,每个敌人都有 a i a_i ai的血,但是突然发现他的技能只能给一个敌人造成1点伤害了。但好在ak机所使用的角色点了天赋,开局时给所有敌人添加debuff,当一名敌人血量降到一半及以下时,就会给所有敌人造成1点伤害(这个debuff触发一次后消失)。ak机想知道,他最少需要多少次攻击才能击杀所有敌人。
输入描述
第一行输入一个正整数 n n n,代表敌人的最大数量
第二行输入 n n n个正整数 a i a_i ai,代表每个敌人的血量
1 ≤ n ≤ 1 0 5 , 1 ≤ a i ≤ 1 0 9 1\le n\le 10^5,1\le a_i \le 10^9 1≤n≤105,1≤ai≤109
输出描述
一个正整数,代表ak机攻击的最小次数。
样例
输入
2
6 8
输出
10
样例说明
先打第一个敌人3次,触发天赋,每个敌人剩余2,7血。接下来,打第二个敌人3次,触发天赋,每个敌人剩余1,3血。然后无法再触发天赋了,因此还要攻击4次,总共是10次。
题解:贪心+排序
为了尽可能减少攻击次数,我们肯定是想把天赋效果尽可能发挥满,也就是说,能被天赋aoe效果击败的怪物,我们就不主动攻击他。
什么怪物必须要攻击呢,显然,天赋最多只能触发 n n n次,如果一个怪物的血量 ≥ n \ge n ≥n,那么一定是要对他进行攻击的。
此外,对于血量更多的一些怪物,即使我们把它留到最后再去攻击(触发完其他所有怪物的aoe效果之后),它本身还是不会触发aoe效果,因此,我们需要将他们的aoe优先触发,这个血量阈值是 2 × n 2\times n 2×n。
剩余的怪物,先触发血量少的怪物的aoe肯定会优于先触发血量多的情况,因为先触发血量多的可能会把血量少的打死,导致aoe伤害浪费,进而导致总攻击次数增多。
所以我们先将怪物按血量排序,然后计算血量大于
2
×
n
2\times n
2×n的怪物的需要手动攻击的攻击次数,同时记录aoe的触发次数cnt
,再依次从小到大依次处理
当我们判断第
i
i
i个敌人时,如果该敌人被天赋攻击cnt
次后不能触发天赋,我们就要打到他触发天赋(血量少的要比血量多的先触发天赋,所以这里一定要打),如果可以触发就省去这一步,之后还要减去后面n-cnt
个敌人天赋触发扣的血,敌人剩余的血量就是我们额外要攻击的次数,进行累加即可。
C++
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int n;
cin >> n; // 输入怪物数量
vector<int> w(n);
for(int i = 0; i < n; i++) {
cin >> w[i]; // 输入每个怪物的血量
}
sort(w.begin(), w.end()); // 对怪物血量进行排序
int cnt = 0; // 记录已经触发的aoe次数
long long ans = 0; // 记录总的攻击次数
while (!w.empty() && w.back() >= 2 * n) { //先处理血量大于2*n的怪物
ans += w.back() - n; // 优先击杀它,并利用它的半血状态触发aoe
w.pop_back(); // 移除已处理的怪物
cnt++; // aoe次数+1
}
for (int x : w) { // 再依次从小到大处理剩余的怪物
int hp_cur = x - cnt; // 当前怪物接受完之前的aoe后的剩余血量
if (hp_cur > x / 2) { // 如果当前怪物没有触发aoe,就攻击他直到触发他的aoe
ans += hp_cur - x / 2;
hp_cur = x / 2;
}
if (hp_cur - (n - cnt) > 0) { // 再计算当前怪物需要的攻击次数
ans += hp_cur - (n - cnt);
}
cnt++; // aoe次数+1
}
cout << ans << endl; // 输出总的攻击次数
return 0;
}
Java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); // 输入怪物数量
List<Integer> w = new ArrayList<>();
for(int i = 0; i < n; i++) {
w.add(sc.nextInt()); // 输入每个怪物的血量
}
Collections.sort(w); // 对怪物血量进行排序
int cnt = 0; // 记录已经触发的aoe次数
long ans = 0; // 记录总的攻击次数
while (!w.isEmpty() && w.get(w.size() - 1) >= 2 * n) { //先处理血量大于2*n的怪物
ans += w.get(w.size() - 1) - n; // 优先击杀它,并利用它的半血状态触发aoe
w.remove(w.size() - 1); // 移除已处理的怪物
cnt++; // aoe次数+1
}
for (int x : w) { // 再依次从小到大处理剩余的怪物
int hp_cur = x - cnt; // 当前怪物接受完之前的aoe后的剩余血量
if (hp_cur > x / 2) { // 如果当前怪物没有触发aoe,就攻击他直到触发他的aoe
ans += hp_cur - x / 2;
hp_cur = x / 2;
}
if (hp_cur - (n - cnt) > 0) { // 再计算当前怪物需要的攻击次数
ans += hp_cur - (n - cnt);
}
cnt++; // aoe次数+1
}
System.out.println(ans); // 输出总的攻击次数
}
}
Python
n = int(input())
w = list(map(int, input().split()))
w.sort()
cnt = 0
ans = 0
while w and w[-1] >= 2*n: #先处理血量大于2*n的怪物
ans += w.pop() - n #即便所有怪物死亡之后,也无法触发它的aoe,因此优先击杀它,并利用它的半血状态触发aoe量
cnt += 1 #记录已经触发的aoe次数
for x in w: #再依次从小到大取数
hp_cur = x - cnt #当前怪物接受完之前的aoe后的剩余血量
if hp_cur > x // 2: #如果当前敌人没有触发aoe,就攻击他直到触发他的aoe
ans += hp_cur - x//2
hp_cur = x//2
if hp_cur - (n-cnt) > 0: #再计算当前敌人需要的攻击次数
ans += hp_cur - (n-cnt)
cnt += 1 #记录的aoe次数+1
print(ans)
第三题:魔法树
在线测评链接:http://121.196.235.151/p/P1062
题目描述
薯条哥是一名远近闻名的魔法师。
这天他受邀来到了一个国家,来帮这个国家的国王解决一个问题。这个国家的国王年轻时曾有一颗非常美丽的树,但是国王却不小心惹怒了一个十分邪恶的黑魔法师,于是黑魔法师施法,让这棵树变得十分丑陋。现在,这棵树一共有 n n n个节点,根节点为 1 1 1号节点,其深度为 1 1 1。每个节点都被黑魔法师随机施加了一个丑陋值,丑陋值越大,越影响这棵树的美观度,影响值为该节点的深度乘其丑陋值。
国王苦苦寻找了半辈子解决方案,让无数魔法师尝试过,但都没办法将树变回原样。长时间受黑魔法影响,这棵树再也无法变为原样了。终于,国王找到了薯条哥,希望薯条哥能尽可能地将树的影响值减少,并承诺,减少了多少影响值,就给薯条哥多少钱。
薯条哥看了看树,发现受黑魔法影响,只能裁剪树的一部分将其嫁接到另一个节点上。薯条哥想挣尽可能多的钱,因此他想知道能将树的影响值降到的最小值是多少?
注:只能裁剪一次。
输入描述
第一行输入一个整数 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \leq n \leq 10^5) n(1≤n≤105)表示的大小
第二行输入 n n n个整数 a ( 1 ≤ a i ≤ 1 0 8 ) a(1 \leq a_i \leq 10^8) a(1≤ai≤108)表示每个节点的丑陋值
接下来 n − 1 n-1 n−1行,每行输入两个整数 u , u ( 1 ≤ u , u ≤ n ) u,u(1 \leq u,u \leq n) u,u(1≤u,u≤n),表示树上的边
输出描述
输出一个整数表示最小的影响值.
样例
输入
4
3 2 1 4
1 2
1 3
3 4
输出
17
说明
裁剪之前,如下图所示
裁剪之后,如下图所示
题解:树形DFS
根据题目描述我们可以发现,树的影响值受到两个因素影响:节点的丑陋值、节点离根节点的距离,因此,我们首先可以去利用上面一道题:有根树的节点数量的思路,来求出每一个节点到根节点的距离。
我们定义 d [ i ] d[i] d[i]为 i i i节点的深度, f [ i ] f[i] f[i]为以 i i i根节点的子树的影响值和,我们可以从根节点1开始,跑一遍DFS,来计算上述的值。
然后我们可以考虑裁剪第 i ( 2 ≤ i ≤ n ) i(2\le i \le n) i(2≤i≤n)个节点,贪心的考虑,一定是把当前的节点,嫁接到根节点的位置上,那么,其实改变的影响值其实就是以 i i i为根节点的子树的影响值对应的部分。
我们需要去分析,第 i i i个节点裁剪前后的影响值的一个变化量。
- 首先,如果第 i i i个节点是1号根节点的子节点,则就算裁剪,也不会对答案产生影响(距离根节点的距离无法再减少了)
- 如果第 i i i个节点不是1号根节点的子节点,我们需要去分析裁剪前后的变化量,大家可以参考下面视频举得一个例子。
我们可以发现,对于第 i i i个节点裁剪后,对应的影响值变化量,是和节点 i i i到根节点的距离 d [ i ] d[i] d[i]以及以节点 i i i为根节点的子树的丑陋值之和有关。
因此,在一开始的dfs
过程中,我们还需要预处理出每一个节点子树的丑陋值之和,记为
t
o
t
a
l
[
i
]
total[i]
total[i]
第 i i i个节点裁剪前:距离根节点的距离为 d [ i ] d[i] d[i]
第 i i i个节点裁剪后:距离根节点的距离为2
因此,对应的变化量即为 ( d [ i ] − 2 ) × t o t a l [ i ] (d[i]-2)\times total[i] (d[i]−2)×total[i]
因此对于第 i i i个节点,裁剪后对应的总的影响值即为 f [ 1 ] − ( d [ i ] − 2 ) × s u m [ i ] f[1]-(d[i]-2)\times sum[i] f[1]−(d[i]−2)×sum[i]
我们枚举裁剪每一个节点对应的影响值,取最小值即为所求答案。
注意:本题数据范围较大,需要使用long long
C++
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int>PII;
typedef long long ll;
const int N=2E5+10,mod=1e9+7;
int n,m;
ll f[N],w[N],d[N],total[N];
vector<int>g[N];
vector<ll> dfs(int u,int fa,int depth){ //预处理f[i],d[i],total[i]数组
d[u]=depth;
total[u]=w[u];
f[u]+=1ll*w[u]*d[u];
for(int &x:g[u]){
if(x==fa)continue;
auto t=dfs(x,u,depth+1);
f[u]+=t[0];total[u]+=t[1];
}
return {f[u],total[u]};
}
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>w[i];
for(int i=1;i<n;i++){
int a,b;
cin>>a>>b;
g[a].push_back(b);
g[b].push_back(a);
}
dfs(1,0,1);
ll res=f[1];
for(int i=2;i<=n;i++){ //考虑替换第i个节点
if(d[i]<=2)continue;
ll down=1ll*(d[i]-2)*total[i];
res=min(res,f[1]-down);
}
cout<<res<<endl;
return 0;
}
Java
import java.util.*;
public class Main {
static final int N = 200010;
static final int mod = (int)1e9 + 7;
static int n;
static long[] f = new long[N], w = new long[N], d = new long[N], total = new long[N];
static List<Integer>[] g = new ArrayList[N];
static long[] dfs(int u, int fa, int depth) {
d[u] = depth;
total[u] = w[u];
f[u] += 1L * w[u] * d[u];
for (int x : g[u]) {
if (x == fa) continue;
long[] t = dfs(x, u, depth + 1);
f[u] += t[0];
total[u] += t[1];
}
return new long[]{f[u], total[u]};
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
for (int i = 1; i <= n; i++) {
w[i] = sc.nextLong();
g[i] = new ArrayList<>();
}
for (int i = 1; i < n; i++) {
int a = sc.nextInt();
int b = sc.nextInt();
g[a].add(b);
g[b].add(a);
}
dfs(1, 0, 1);
long res = f[1];
for (int i = 2; i <= n; i++) {
if (d[i] <= 2) continue;
long down = 1L * (d[i] - 2) * total[i];
res = Math.min(res, f[1] - down);
}
System.out.println(res);
}
}
Python
import sys
sys.setrecursionlimit(10**6) # 设置递归深度上限
from collections import defaultdict
N = 200010
mod = int(1e9) + 7
n = 0
f = [0] * N
w = [0] * N
d = [0] * N
total = [0] * N
g = defaultdict(list)
def dfs(u, fa, depth):
d[u] = depth
total[u] = w[u]
f[u] += 1 * w[u] * d[u]
for x in g[u]:
if x == fa:
continue
t = dfs(x, u, depth + 1)
f[u] += t[0]
total[u] += t[1]
return f[u], total[u]
n = int(input())
w=[0]+list(map(int,input().split()))
for i in range(1, n):
a, b = map(int, input().split())
g[a].append(b)
g[b].append(a)
dfs(1, 0, 1)
res = f[1]
for i in range(2, n + 1):
if d[i] <= 2:
continue
down = 1 * (d[i] - 2) * total[i]
res = min(res, f[1] - down)
print(res)