文章目录
前言
本篇博客是2024牛客算法冬令营第二次训练赛的题解,不包括K、L、M、G、H,包括题目已经在目录中列出
A、Tokitsukaze and Bracelet
1.题目描述
《绯染天空》是一款由 key 社与飞机社共同开发的角色扮演游戏,剧情内容由著名的剧本作家麻枝准编写。它是一款氪金手游,但也有 steam 端。直至今日(2024.2.5),Tokitsukaze 入坑短短6个月,时长却已达到了382小时。
游戏中有着一个’‘饰品炼成’‘系统,通过消耗宝石,可以炼成对应的饰品。’‘饰品炼成’'系统有关手环的信息介绍如下:
手环有 3 种属性:普通攻击百分比加成,体力,精神。每次炼成手环时,会对手环的每个属性都随机赋予强化等级,每个属性的强化等级可能为+0, +1, +2。强化等级对应的属性值如下:
- 对于普通攻击百分比加成来说:+0 为 100%,+1为 150%,+2为 200%;
- 对于体力和精神来说:+0 会在 {29,30,31,32}里随机选择,+1 会在 {34,36,38,40}里随机选择, +2 固定为 45。
例如,一个普通攻击百分比加成 100%,体力 45,精神 40 的手环的强化等级为 +3。其中普通攻击力百分比提供了 +0,体力提供了 +2,精神提供了 +1。
下图是一个顶级属性的手环:
现在 Tokitsukaze 炼成了 n 个手环,她只知道每个手环的属性,请你告诉她每个手环的强化等级是多少。
输入描述:
第一行包含一个整数 n (1≤n≤100),表示 Tokitsukaze 炼成了 n 个手环。
接下来 nnn 行,每行三个整数 ai, bi, ci (ai∈{100,150,200}; bi, ci∈{29,30,31,32,34,36,38,40,45}),表示第 i 个手环的数值:普通攻击百分比加成 ai%, 体力 bi, 精神 ci。
输出描述:
输出 n 行,每行包含一个整数,第 i 行表示第 i 个手环的强化等级。
输入
5
100 29 29
100 32 38
150 45 40
200 45 45
100 45 40
输出
0
1
4
6
3
说明
样例解释:
第 1 个手环的属性为:普通攻击百分比加成 100%,体力 29,精神 29。普通攻击力百分比提供了 +0,体力提供了 +0,精神提供了 +0,所以该手环的强化等级为 +0;
第 2 个手环的属性为:普通攻击百分比加成 100%,体力 32,精神 38。普通攻击力百分比提供了 +0体力提供了 +0,精神提供了 +1,所以该手环的强化等级为 +1;
第 3 个手环的属性为:普通攻击百分比加成 150%,体力 45,精神 40。普通攻击力百分比提供了 +1,体力提供了 +2,精神提供了 +1,所以该手环的强化等级为 +4;
第 4 个手环的属性为:普通攻击百分比加成 200%,体力 454545,精神 45。普通攻击力百分比提供了 +2,体力提供了 +2,精神提供了 +2,所以该手环的强化等级为 +6;
第 5 个手环的属性为:普通攻击百分比加成 100%,体力 45,精神 40。普通攻击力百分比提供了 +0,体力提供了 +2,精神提供了 +1,所以该手环的强化等级为 +3。
2.题解
- 签到题,按照题意模拟即可
#include<bits/stdc++.h>
using namespace std;
const int x[4]={34,36,38,40};
void solve()
{
int a,b,c;
cin >> a >> b >> c;
int sum=0;
if(a==150) sum += 1;
else if(a==200) sum += 2;
for(int i=0;i<4;i++)
{
if(b==x[i]) sum += 1;
if(c==x[i]) sum += 1;
}
if(b==45) sum += 2;
if(c==45) sum += 2;
cout << sum <<endl;
}
int main()
{
ios_base::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int T;
cin >> T;
while(T--)
{
solve();
}
}
B、Tokitsukaze and Cats
1.题目描述
众所周知 Tokitsukaze 特别懒,在家从不搞卫生,搞卫生这事都是由 Tokitsukaze 的老婆 TomiokapEace 一手包办。在 TomiokapEace 搞卫生时,它们总是会上窜下跳,特别碍事,于是她想做一些措施让它们无法移动。
现在把 Tokitsukaze 的家看作是一个 n×m 的网格,第 i 只猫的位置在 (xi,yi)。TomiokapEace 想用若干片防猫网来限制猫的移动,她将在一只猫所在格子的四周各放上一片防猫网。
一片防猫板是位于两个相邻格子间隔,长度为1个单位的障碍物,可以阻止猫移动。具体来讲,当猫位于 (x,y)时,若(x−1,y), (x,y−1), (x+1,y), (x,y+1) 中的任意一格和 (x,y) 之间不存在防猫板,则猫可以向相邻格子移动。
TomiokapEace 想知道至少需要购买多少片防猫网才能使所有猫无法移动。
PS:现实中确实买了防猫网,塑料的,上面有尖尖的刺。本以为它们不敢踩上去,谁知道根本防不了一点,直接给你表演精准踩到刺与刺的空隙间。对于它们来说,这玩意也只不过是一个减速带罢了(悲)。
输入描述:
第一行包含三个整数 n, m, k (1≤n,m≤300; 1≤k≤n⋅m),表示 Tokitsukaze 家的大小为 n×m,以及家里有 k 只猫。
接下来 k 行,每行两个整数 xi, yi (1≤xi≤n; 1≤yi≤m ),表示第 i 只猫的位置。保证所有猫的位置互不相同。
输出描述:
输出一个整数,表示 TomiokapEace 至少需要购买防猫网的数量。
输入
5 4 3
3 2
4 3
4 4
输出
11
说明
样例1如图,这三只猫至少需要 11 片防猫网(黑色边界为防猫网)。你可以认为每只猫是独立的,可以随便移动,移动到别的猫所在位置也算移动。所以即使两只猫相邻,中间也需要有防猫网。
输入
1 1 1
1 1
输出
4
2.题解
- 签到题,遍历加判断即可
#include<bits/stdc++.h>
using namespace std;
bool b[310][310];
int main()
{
ios_base::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int n,m,k;
cin >> n >> m >> k;
int sum = 0;
while(k--)
{
int x,y;
cin >> x >> y;
int s = 4;
if(b[x-1][y]==1) s--;
if(b[x+1][y]==1) s--;
if(b[x][y-1]==1) s--;
if(b[x][y+1]==1) s--;
b[x][y]=1;
sum += s;
}
cout << sum << endl;
}
C、Tokitsukaze and Min-Max XOR
1.题目描述
Tokitsukaze 有一个长度为 n 的序列 a1,a2,…,an 和一个整数 k。
她想知道有多少种序列 b1,b2,…,bm,满足:
- 1≤bi≤n
- bi−1<bi (2≤i≤m)
- min(ab1,ab2,…,abm)⊕max(ab1,ab2,…,abm)≤k
其中 ⊕ 为按位异或,具体参见 百度百科:异或
答案可能很大,请输出 mod 109+7 后的结果。
输入描述:
第一行包含一个整数 T (1≤T≤2⋅105),表示 T 组测试数据。
对于每组测试数据:
第一行包含两个整数 n, k (1≤n≤2⋅105; 0≤k≤109)。
第二行包含 n 个整数 a1,a2,…,an(0≤ai≤109)。
保证 ∑n 不超过 2⋅105。
输出描述:
对于每组测试数据,输出一个整数,表示答案 mod 109+7 后的结果。
输入
3
3 2
1 3 2
5 3
1 3 5 2 4
5 0
0 0 0 0 0
输出
6
10
31
2.题解
- 我们要将其构造为0-1trie树(字典树、前缀树),从顶向下依次是从高位到低位(化为二进制)
- 我们从小到大遍历每一个trie树中有的数(也就是题目给的数),每次把它作为选取区间的最大值
- 由于我们每次选取为最大值,所以另一个值只会在该值的左边,所以我们可以边计算每种情况边逐步构建该0-1trie树,所以代码中to[N * 35][2]就是每个节点左右点所对应的下标
- 计算每种情况我们可以用快速幂和逆元(费马小定理)来减低时间复杂度,所以val[N * 35]就是存每个点的逆元权重
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
int to[N * 35][2]; //节点左右子节点的下标
int val[N * 35]; //逆元权重
int tot = 0;
void insert(int x,int c)
{
int p = 0; //根
for (int i = 30; i >= 0; i--)
{
int v = (x >> i & 1);
if (!to[p][v])
{
to[p][v] = ++tot; //实时更新tot表示已经有多少节点
}
val[to[p][v]] = (val[to[p][v]] + c) % mod;
p = to[p][v];
}
}
int sum(int x,int k) //x是每次遍历假设的区间最大值,k就是题目k的意思
{
int p = 0, res = 0;
//可以根据0-1trie树的性质判断
for (int i = 30; i >= 0; i--)
{
int vx = (x >> i & 1), vk = (k >> i & 1);
if (vk == 1 && vx == 1)
{
res = (res + val[to[p][1]]) % mod;
p = to[p][0];
}
else if (vk == 1 && vx == 0)
{
res = (res + val[to[p][0]]) % mod;
p = to[p][1];
}
else
{
p = to[p][vx];
}
if (!p) break;
if (!i) res = (res + val[p]) % mod;
}
return res;
}
int ksm(int x,int n)
{
int res = 1;
while (n)
{
if (n & 1) res = res * x % mod;
x = x * x % mod;
n >>= 1;
}
return res;
}
void solve()
{
int n,k; cin >> n >> k;
vector<int> a(n);
for (auto &x:a) cin >> x;
sort(a.begin(), a.end());
tot = 0;
for (int i = 0; i <= n * 32; i++)
{
val[i] = 0;
to[i][0] = to[i][1] = 0;
}
int ans = 0;
for (int i = 1; i <= n; i++)
{
//+1是因为a[i-1]自身也算一个
ans = (ans + 1 + ksm(2,i - 1) * sum(a[i - 1],k) % mod) % mod;
insert(a[i - 1],ksm(ksm(2,i),mod - 2)); //插入
}
cout << ans << '\n';
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t = 1;
cin >> t;
while (t--) solve();
return 0;
}
D、Tokitsukaze and Slash Draw
1.题目描述
在游戏王中有一张魔法卡叫做「一击必杀!居合抽卡」。
简单来说,当你发动这张卡后,你会从卡组最上方抽取一张卡,如果那张卡是「一击必杀!居合抽卡」的情况下,有大概率’‘一击必杀’‘打败对手。通常这张卡被玩家们称作’‘拔刀’'。
在游戏王中,许多卡拥有着能够调整卡组中卡片顺序的效果,例如:「魔救」系列。
这个系列的卡大多包含’‘从自己卡组上面把5张卡翻开,可以…,剩下的卡用喜欢的顺序回到卡组最下面’'的效果。也就是说,可以通过「魔救」系列的卡片效果,来调整「一击必杀!居合抽卡」在卡组中的位置。
— 虽然那天我赢了很多局,但是当他拔刀的那一刻我才发现,他赢我太多了。
‘‘哇哦,用魔救拔刀也太帅了吧’’,Tokitsukaze 心里想道。她关闭知乎,打开《Yu-Gi-Oh Master Duel》准备学习如何操作,现在她正在人机练习中。
Tokitsukaze 的卡组有 n 张卡片,她已经知道「一击必杀!居合抽卡」这张卡片是在卡组中从最下面往上数的第 k 张卡片。她挑选了 m 种能够调整卡组中卡片顺序的卡片,第 i 种类型的卡片效果是:把卡组最上方的 ai 张卡片拿出来按顺序放到卡组最下方。这个效果你可以理解为:首先将一张卡片拿出来,然后把这张卡片放到卡组最下方,这个动作重复做 ai次。而发动第 i 种类型的卡片效果,需要支付 bi 的’‘cost’'。
Tokitsukaze 可以通过一些操作使每种类型的卡片可以发动无限次,她是否能够让「一击必杀!居合抽卡」这张卡片出现在卡组的最上方?如果能,请输出最少需要支付的’‘cost’‘,如果不能,请输出 ‘’-1’'(不带引号)。
输入描述:
第一行包含一个整数 T (1≤T≤1000),表示 T 组测试数据。
对于每组测试数据:
第一行包含三个整数 n, m, k (1≤n≤5000; 1≤m≤1000; 1≤k≤n),表示 Tokitsukaze 的卡组有 n 张卡片,她挑选了 m 种能够调整卡组中卡片顺序的卡片,以及「一击必杀!居合抽卡」这张卡片是在卡组中从最下面往上数的第 k 张卡片。
接下来 m 行,每行两个整数 ai, bi (1≤ai≤n, 1≤bi≤109),表示第 i 种类型的卡片能将卡组最上方的 ai 张卡片拿出来按顺序放到卡组最下方,并且需要支付bi 的"cost"才能发动。
保证 ∑n 不超过 5000,∑m 不超过 1000。
输出描述:
对于每组测试数据,如果 Tokitsukaze 能够让「一击必杀!居合抽卡」这张卡片出现在卡组的最上方,输出一个整数表示最少需要支付的"cost";否则输出 “-1”(不带引号)。
输入
2
10 1 7
2 100
10 3 7
2 3
6 1
5 10
输出
-1
13
2.题解
- dijkstra求最短路问题
- 我们可以注意到问题可以转化为一个图,我们最终要求的就是k到n的最短路,权值就是每次卡牌所需费用
#include<bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
const int N = 5e3 + 10, M = 1e3 + 10;
int n, m, k;
int a[M], b[M];
ll dis[N];
bool vis[N];
void dijkstra(int n, int k)
{
dis[k] = 0;
for(int i = 1; i <= n; i ++ )
{
int u = 0;
ll mind = inf;
for(int j = 1; j <= n; j ++ )
if(!vis[j] && dis[j] < mind)
u = j, mind = dis[j];
if(u == 0) break;
vis[u] = true;
for(int j = 1; j <= m; j ++ )
{
int v = (u + a[j] <= n? u + a[j]: u + a[j] - n);
if(dis[v] > dis[u] + b[j]) dis[v] = dis[u] + b[j];
}
}
}
void solve()
{
cin >> n >> m >> k;
for(int i = 1; i <= m; i ++ ) cin >> a[i] >> b[i];
for(int i = 1; i <= n; i ++ ) dis[i] = inf, vis[i] = false;
dijkstra(n, k);
if(dis[n] == inf) dis[n] = -1;
cout << dis[n] << endl;
}
signed main()
{
ios_base::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int T;
cin >> T;
while(T--) solve();
}
E、Tokitsukaze and Eliminate (easy)
1.题目描述
easy 与 hard 的唯一区别是 coli 的范围。
Tokitsukaze 正在玩一个消除游戏。
初始有 n 个宝石从左到右排成一排,第 i 个宝石的颜色为 coli。Tokitsukaze 可以进行若干次以下操作:
- 任选一种颜色 x,将颜色为 x 的最右边那颗宝石、以及该宝石右边的所有宝石全部消除。
Tokitsukaze 想知道至少需要几次操作才能把 n 个宝石全部消除。
输入描述:
第一行包含一个整数 T (1≤T≤2⋅105),表示 T 组测试数据。
对于每组测试数据:
第一行包含一个整数 n (1≤n≤2⋅105),表示初始宝石的数量。
第二行包含 n 个整数 col1,col2,…,coln (1≤coli≤min(n,2)),表示每个宝石的颜色。
保证 ∑n 不超过 2⋅105。
输出描述:
对于每组测试数据,输出一个整数,表示把 n 个宝石全部消除所需要的最少操作次数。
输入
4
3
1 2 1
5
1 2 2 1 2
11
2 2 1 2 2 1 1 2 2 1 2
1
1
输出
2
2
6
1
2.题解
- 贪心求最优问题
- 从后往前遍历,每次都取最后一个未出现元素(1或者2)的第一个位置
- 最后再将余下的纯1或者纯2一个个处理,所以直接加其个数即可
#include<bits/stdc++.h>
#define int long long
using namespace std;
void solve()
{
int n;
cin >> n;
int x[n];
for(int i=0;i<n;i++) cin >> x[i];
int sum = 0;
int m1 = n;
for(int i=n-1;i>=1;i--)
{
if(x[i]*x[i-1]==2)
{
sum ++ ;
i --;
m1 = i;
}
}
sum += m1;
cout << sum << "\n";
}
signed main()
{
ios_base::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int T;
cin >> T;
while(T--) solve();
}
F、Tokitsukaze and Eliminate (hard)
1.题目描述
easy 与 hard 的唯一区别是 coli 的范围。
Tokitsukaze 正在玩一个消除游戏。
初始有 n 个宝石从左到右排成一排,第 i 个宝石的颜色为 coli。Tokitsukaze 可以进行若干次以下操作:
- 任选一种颜色 x,将颜色为 x 的最右边那颗宝石、以及该宝石右边的所有宝石全部消除。
Tokitsukaze 想知道至少需要几次操作才能把 n 个宝石全部消除。
输入描述:
第一行包含一个整数 T (1≤T≤2⋅105),表示 T 组测试数据。
对于每组测试数据:
第一行包含一个整数 n (1≤n≤2⋅105),表示初始宝石的数量。
第二行包含 n 个整数 col1,col2,…,coln (1≤coli≤n),表示每个宝石的颜色。
保证 ∑n 不超过 2⋅105。
输出描述:
对于每组测试数据,输出一个整数,表示把 n 个宝石全部消除所需要的最少操作次数。
输入
6
3
1 2 1
5
1 2 2 1 2
11
2 2 1 2 2 1 1 2 2 1 2
1
1
5
1 2 3 4 5
10
4 1 5 5 2 2 5 2 4 2
输出
2
2
6
1
1
2
2.题解
- 贪心求最优问题
- 用两个map存储,一个mp存各个元素及其对应的个数,一个mp1存每轮遍历的元素和其个数
- 从后往前遍历,如果mp1.size()刚好等于mp.size则满足一轮遍历(在遍历中要更新mp和mp1,并用erase删除mp遍历完的元素),如此从后向前遍历一轮即可
- 最后再将余下的纯数一个个处理,所以直接加其个数即可
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5+10;
int a[N];
void solve()
{
int n;
cin >> n;
map<int,int> mp,mp1;
int ans = 0;
for(int i=1;i<=n;i++)
{
cin >> a[i];
mp[a[i]]++;
}
int s = mp.size();
for(int i=n;i>=1;i--)
{
mp[a[i]]--;
mp1[a[i]]++;
if(mp[a[i]]==0) mp.erase(a[i]);
if(mp1.size()==s)
{
ans++;
s=mp.size();
mp1.clear();
}
}
cout << ans <<endl;
}
signed main()
{
ios_base::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int T;
cin >> T;
while(T--) solve();
}
I、Tokitsukaze and Short Path (plus)
1.题目描述
plus 与 minus 的唯一区别是 边权的计算方式。
Tokitsukaze 有一张 n 个顶点的完全图 G, 顶点编号是 1 到 n,编号为 i 的顶点的值是 ai。
完全图指的是每对顶点之间都恰好有一条无向边的图。对于顶点 u 和顶点 v 之间的无向边,边权计算方式如下:
wu,v:u=v时为0,u!=v时为∣au+av∣+∣au−av∣
定义 dist(i,j) 表示顶点 i 为起点,顶点 j 为终点的最短路。求 ∑i=1n∑j=1ndist(i,j)
关于最短路的定义:
定义一条从 s 到 t 的路径为若干条首尾相接的边形成的序列且该序列的第一条边的起点为 s,最后一条边的终点为 t,特别的,当 s=t 时该序列可以为空。
定义一条从 s 到 t 的路径长度为该路径中边权的总和。
定义 s 到 t 的最短路为 s 到 t 所有路径长度中的最小值。
输入描述:
第一行包含一个整数 T (1≤T≤2⋅105),表示 T 组测试数据。
对于每组测试数据:
第一行包含一个整数 n (1≤n≤2⋅105),表示完全图 G 的顶点数量。
第二行包含 n 个整数 a1,a2,…,an (1≤ai≤2⋅105),表示完全图 G 的每个顶点的值。
保证 ∑n 不超过 2⋅105。
输出描述:
对于每组测试数据,输出一个整数表示答案。
输入
5
1
1
3
10 1 100
5
1 2 3 4 5
4
2 3 5 8
5
1 3 3 4 5
输出
0
840
160
148
164
2.题解
- 我们可以找到特性,任意两点之间的最短路就是两点中较大值的两倍
- 所以两点之间最短距离一定不仅过其他点,两点之间的最短距离就是较大值的两倍
#include<bits/stdc++.h>
#define int long long
using namespace std;
void solve()
{
int n;
cin >> n;
vector<int> a(n);
for(int i = 0; i < n; i ++ ) cin >> a[i];
sort(a.begin(),a.end());
int sum = 0;
for(int i = 0; i < n; i ++ ) sum += a[i] * 2 * i;
cout << sum * 2 << endl;
}
signed main()
{
ios_base::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int T;
cin >> T;
while(T--) solve();
}
J、Tokitsukaze and Short Path (minus)
1.题目描述
plus 与 minus 的唯一区别是 边权的计算方式。
Tokitsukaze 有一张 n 个顶点的完全图 G, 顶点编号是 1 到 n,编号为 i 的顶点的值是 ai。
完全图指的是每对顶点之间都恰好有一条无向边的图。对于顶点 u 和顶点 v 之间的无向边,边权计算方式如下:
wu,v:u=v时为0,u!=v时为∣au+av∣-∣au−av∣
定义 dist(i,j) 表示顶点 i 为起点,顶点 j 为终点的最短路。求 ∑i=1n∑j=1ndist(i,j)
关于最短路的定义:
定义一条从 s 到 t 的路径为若干条首尾相接的边形成的序列且该序列的第一条边的起点为 s,最后一条边的终点为 t,特别的,当 s=t 时该序列可以为空。
定义一条从 s 到 t 的路径长度为该路径中边权的总和。
定义 s 到 t 的最短路为 s 到 t 所有路径长度中的最小值。
输入描述:
第一行包含一个整数 T (1≤T≤2⋅105),表示 T 组测试数据。
对于每组测试数据:
第一行包含一个整数 n (1≤n≤2⋅105),表示完全图 G 的顶点数量。
第二行包含 n 个整数 a1,a2,…,an (1≤ai≤2⋅105),表示完全图 G 的每个顶点的值。
保证 ∑n 不超过 2⋅105。
输出描述:
对于每组测试数据,输出一个整数表示答案。
输入
5
1
1
3
10 1 100
5
1 2 3 4 5
4
2 3 5 8
5
1 3 3 4 5
输出
0
16
64
64
64
2.题解
- 我们可以找到特性,两点之间的最短路是较小值的两倍
- 所以我们可以知道最小值和任意一点的最短距离都是较小值的两倍
- 其他任意两点都可以通过一个最小值在中间连接(因为是完全图),所以我们可以比较两点较小值的两倍和最小值的四倍,取两者的min即为最短路
#include<bits/stdc++.h>
#define int long long
using namespace std;
void solve()
{
int n;
cin >> n;
vector<int> a(n + 1);
for(int i = 1; i <= n; i ++ ) cin >> a[i];
sort(a.begin(), a.end());
int sum = 0;
sum += 2 * a[1] * (n-1);
for(int i = 2; i <= n; i ++ )
for(int j = i + 1; j <= n; j ++ )
{
if(2 * a[i] > 4 * a[1]) sum += 4 * a[1];
else sum += 2 * a[i];
}
cout << sum * 2 << endl;
}
signed main()
{
ios_base::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int T;
cin >> T;
while(T--) solve();
}