目录
总览
难度 | 算法/知识点 | First Blood | 出题人 | |
---|---|---|---|---|
A | 1-2 | 数学 or 暴力枚举 | Lihg | Octal |
B | 5 | Dijkstra+DP 或 分层图Dijkstra | fexla | Freya |
C | 2 | 排序、贪心 | Lihg | Felix |
D | 4 | 01背包、贪心 | fexla | Felix |
E | 4 | 构造 or 模拟 | Octal | |
F | 3 | 线性DP or 记忆化搜索 | JALJAYO | Freya |
G | 1 | 循环语句 | cqsbz_cxy | Felix |
H | 1 | if语句 | JALJAYO | Freya |
这次比赛严重歪榜了,B D有人先做出来,F却到最后1h才有人AC
各题题解
A+B Problem
难度(暴力):入门(1)
难度(正解):普及-(2)
本题作者非常善良,没有卡 O ( n ) O(n) O(n)暴力算法(即循环区间内每一个数找另一个数的算法)
以下是 O ( 1 ) O(1) O(1)正解:
由第一个数的范围 [ a , b ] [a,b] [a,b]和两数之和 N N N,可以得到第二个数的范围必须为 [ N − b , N − a ] [N-b,N-a] [N−b,N−a]。
根据题意,第二个数还要在 [ c , d ] [c,d] [c,d]内,因此最终答案数是这两个区间的交集,即 [ m a x ( N − b , c ) , m i n ( N − a , d ) ] [max(N-b,c), min(N-a, d)] [max(N−b,c),min(N−a,d)]中的整数个数
答案是 m i n ( N − a , d ) − m a x ( N − b , c ) + 1 min(N-a, d)-max(N-b,c)+1 min(N−a,d)−max(N−b,c)+1,可以 O ( 1 ) O(1) O(1)地求出一组答案。
C++题解:
#include<bits/stdc++.h>
using namespace std;
using ll = long long; //其实不开long long也能过
int main()
{
ios::sync_with_stdio(false);
int T; cin >> T;
while(T--)
{
ll n; cin>>n;
ll a,b,c,d; cin>>a>>b>>c>>d;
ll dd = n - a;
ll cc = n - b;
ll ans = min(dd, d) - max(cc, c) + 1;
cout << max(ans, 0ll) << "\n";
}
return 0;
}
Breath of the Wild
难度:提高+/省选(5)
简明题意:在带权有向图中寻找一条从起点到终点的最短路径,但是我们还需要考虑风弹技巧,即最多有K次机会可以让一条边的权重变为0。
我们可以使用
D
i
j
k
s
t
r
a
Dijkstra
Dijkstra算法解决这个问题。
D
i
j
k
s
t
r
a
Dijkstra
Dijkstra算法本身是用来求解单源最短路径问题的,一般情况下dis[i]
维护从起点到i
的距离,但在本题中,由于有风弹这么个恶心玩意儿,所以我们需要加一个维度,用dis[i][j]
维护从起点到i
,还剩下j
次风弹可以使用的最短距离。
我们用一个优先队列(小顶堆)来维护所有的状态,队列中的状态按照 dis 的值从小到大排列。
在每一步,我们从队列中取出一个状态,如果这个状态的 i
是终点,那么 dis[i][j]
就是答案。否则,我们需要考虑从当前的塔i
走到所有相邻的塔nxt
的状态,有两种情况:
-
不使用风弹:
dis[nxt][j]=dis[i][j]+a[i][j]
,其中a[i][j]
是i
到j
的边权。 -
使用风弹:
dis[nxt][j]=dis[i][j-1]
综合用以上两种情况更新dis[nxt][j]
,得到状态转移方程dis[nxt][j]=min({dis[nxt][j], dis[i][j]+a[i][j], dis[nxt][j]=dis[i][j-1]})
(对,
D
i
j
k
s
t
r
a
Dijkstra
Dijkstra算法的本质就是DP!!!)
(也可以设dis[i][j]
为从起点到i
,使用了j
次风弹的最短距离,状态转移的j-1
变j+1
即可)
C++题解 By Fexla:
//
// Created by fexla on 2023/5/28.
//
#include <bits/stdc++.h>
using namespace std;
struct v {
vector<int> nxt;
vector<int> c;
};
typedef pair<int, int> pi;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout << setprecision(10) << fixed;
int n, m, k, s, t;
cin >> n >> m >> k >> s >> t;
vector<v> g(n);
for (int i = 0; i < m; ++i) {
int a, b, c;
cin >> a >> b >> c;
g[a].nxt.push_back(b);
g[a].c.push_back(c);
g[b].nxt.push_back(a);
g[b].c.push_back(c);
}
vector<vector<int>> dis(k + 1);
for (int i = 0; i < k + 1; ++i) {
dis[i] = vector<int>(n, 1e9);
}
dis[0][s] = 0;
priority_queue<pi, vector<pi>, greater<pi>> q;
for (int i = 0; i < g[s].nxt.size(); ++i) {
int cost = g[s].c[i];
int nxt = g[s].nxt[i];
q.push({cost, nxt});
if(k != 0) {
q.push({0, 1e7 + nxt});
}
}
while (!q.empty()) {
auto item = q.top();
q.pop();
int kk = item.second / 10000000;
int nxt = item.second % 10000000;
int cost = item.first;
if(dis[kk][nxt] <= cost) {
continue;
}
dis[kk][nxt] = cost;
for (int i = 0; i < g[nxt].nxt.size(); ++i) {
int ccost = g[nxt].c[i];
int nnxt = g[nxt].nxt[i];
if(kk + 1 <= k && dis[kk + 1][nnxt] > cost) {
q.push({cost, (kk + 1) * 10000000 + nnxt});
}
if(dis[kk][nnxt] > cost + ccost) {
q.push({cost + ccost, kk * 10000000 + nnxt});
}
}
}
int ans = 1e9;
for (int i = 0; i <= k; ++i) {
ans = min(ans, dis[i][t]);
}
cout << ans;
return 0;
}
Python题解:
import heapq
def solve(N, M, K, s, t, roads):
graph = [[] for _ in range(N)]
for a, b, c in roads:
graph[a].append((b, c))
graph[b].append((a, c))
dist = [[float('inf')] * (K + 1) for _ in range(N)]
dist[s][K] = 0
pq = [(0, s, K)]
while pq:
d, node, k = heapq.heappop(pq)
if node == t:
return d
if d > dist[node][k]:
continue
for neighbor, weight in graph[node]:
if k > 0 and dist[neighbor][k - 1] > d:
dist[neighbor][k - 1] = d
heapq.heappush(pq, (d, neighbor, k - 1))
if dist[neighbor][k] > d + weight:
dist[neighbor][k] = d + weight
heapq.heappush(pq, (d + weight, neighbor, k))
return min(dist[t])
# 测试用例
n,m,k = map(int, input().split())
s,t = map(int, input().split())
road = []
for i in range(m):
a,b,c = map(int, input().split())
road.append((a,b,c))
print(solve(n,m,k,s,t,road))
Cells At Work
难度:普及-(2)
很鲜明的贪心,把细菌从多到少排序,依次消灭。用一个变量factor
记录当前倍率,每消灭一种细菌,factor加倍并对MOD取模:factor=factor*2%MOD
。排序后,消灭第i
种细菌所用的细胞数为a[i]*factor
,复杂度
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。
常见问题1:取模——必须每一步计算都取模,否则很容易爆long long,即使您使用Python,对大数的计算会很慢,会TLE。
多bb两句:如果没维护factor
变量,每次使用
O
(
l
o
g
n
)
O(logn)
O(logn)的快速幂,最终复杂度是
O
(
n
l
o
g
n
l
o
g
n
)
O(nlognlogn)
O(nlognlogn)。本来想卡掉这个复杂度,后来一想,会快速幂也是种本事,就给过了QwQ。
Python题解:
n = int(input())
MOD = 19260817
bac = list(map(int, input().split()))
bac.sort(reverse=True)
factor = 1
cell = 0
for i in bac:
died = factor * i % MOD
cell = (cell + died) % MOD
factor = factor * 2 % MOD
print(cell)
C++题解:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MOD = 19260817;
ll a[2400000];
int n;
ll factor = 1, ans;
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
sort(a + 1, a + n + 1);
reverse(a + 1, a + n + 1); //按从大到小排序
for (int i = 1; i <= n; i++)
{
ans = ans % MOD + a[i] * factor % MOD;
factor = (factor << 1) % MOD; //等价于factor*2%MOD
}
printf("%lld\n", ans % MOD);
return 0;
}
Discount Store
难度:普及-/提高(3)
解法:01背包(DP)+贪心
先把商品按价格排序。这样,从贵到便宜考虑每一件商品,如果购买则可“获得优惠券(0元购资格)”,即有资格领取一件后面的商品
f[i][j][0]
代表考虑前i件物品、j元钱、没有优惠券,f[i][j][1]
代表考虑前i件物品、j元钱、有优惠券
一个小小的贪心(也是本题难点):任何时候不会拥有2张优惠券(商品价格A>B>C>D,如果ABCD都想要,应该买A送B,买C送D;任何时候都不会原价买两件更贵的商品)
考虑以下两种情况下的状态转移:
当前没有优惠券:1. 考虑该商品之前有优惠券,用券免费换了 2. 之前没有优惠券,且不买该商品,因此 f[i][j][0] = max(f[i-1][j][0], f[i-1][j][1] + item[i].v)
;
当前有优惠券:1. 考虑该商品之前没有优惠券,原价购买商品 2. 之前有优惠券,且不买(兑换)该商品,因此 f[i][j][1] = max(f[i-1][j][1], f[i-1][j-item[i].w][0] + item[i].v)
。注意判断j
要大于商品价格item[i].w
,否则会数组越界。
C++题解:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 505;
const int MAXW = 2005;
int n, W;
struct Item {
int w, v;
}item[MAXN];
int f[MAXN][MAXW][2];
int main()
{
cin >> n >> W;
for (int i = 1; i <= n; i++)
cin >> item[i].w >> item[i].v;
memset(f, 0, sizeof(f));
for (int i = 0; i <= W; i++)
f[0][i][1] = -0x3f3f3f3f;
sort(item+1, item+n+1, [](Item a, Item b){return a.w > b.w;});
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= W; j++)
{
f[i][j][0] = max(f[i-1][j][0], f[i-1][j][1] + item[i].v);
f[i][j][1] = f[i-1][j][1];
if(j >= item[i].w) {
f[i][j][1] = max(f[i][j][1], f[i-1][j-item[i].w][0] + item[i].v);
}
}
}
cout << max(f[n][W][0], f[n][W][1]) << endl;
return 0;
}
Exchange Card
难度:提高/省选-(4)
解法:构造 or 超级无敌大枚举
神仙题,我也写不出来QAQ,虽然没啥算法但是码量/思维量太大+一亿个细节呜呜呜呜呜。等出题大佬の官方题解。
出题人Octal官方题解:构造,分别考虑2个人换和3个人换的情况。
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int, int>
#define pdd pair<double, double>
#define pcc pair<char, char>
typedef long long ll;
inline void solve()
{
int n;
cin >> n;
map<pcc, vector<int>> a;
vector<tuple<int, char, int, char>> ans;
vector<string> s(n);
for(int i=0; i<n; i++)
cin >> s[i];
//两人换
for(int i=0; i<n; i++)
{
map<char, int> cnt;
for(auto x : s[i])
cnt[x] ++;
vector<char> need, give;
for(auto x : {'Y', 'N', 'U'})
if(cnt[x] == 0)
need.push_back(x);
else
for(int j=2; j<=cnt[x]; j++)
give.push_back(x);
for(int j=0; j<need.size(); j++)
{
char x = need[j];
char y = give[j];
if(!a[{y, x}].empty())
{
ans.emplace_back(i, y, a[{y, x}].back(), x);
a[{y, x}].pop_back();
}
else a[{x, y}].push_back(i);
}
}
//三人换
char ch[3] = {'Y', 'N', 'U'};
if(a[{ch[0], ch[1]}].empty())
swap(ch[0], ch[1]);
for(int i=0; i<a[{ch[0], ch[1]}].size(); i++)
{
int x = a[{ch[0], ch[1]}][i];
int y = a[{ch[1], ch[2]}][i];
int z = a[{ch[2], ch[0]}][i];
ans.emplace_back(x, ch[1], y, ch[2]);
ans.emplace_back(x, ch[2], z, ch[0]);
}
cout << ans.size() << endl;
for(auto t : ans)
{
int a = get<0>(t);
char b = get<1>(t);
int c = get<2>(t);
char d = get<3>(t);
printf("%d %c %d %c\n", a+1, b, c+1, d);
}
}
int main()
{
int t;
cin >> t;
while(t--)
solve();
return 0;
}
Fine! Stop coding! Just go sleeping!
难度:普及/提高-(3)
(F题没人做,我和我的小伙伴们都惊呆了!这题比B D简单太多,反而难题有不少大佬过了……这次比赛严重歪榜了QAQAQAQAQ)
解法1:
线性DP,设dp[i]
表示从第i分钟开始的最少工作时间,最终答案即为n-dp[1]
。
首先令dp
数组全为正无穷,置dp[n+1]=0
(注意:不能置dp[n]=0
,因为可能第n分钟来一个1分钟完成的任务,这种情况下dp[n]=1
),然后从n
到1
枚举每分钟。
对于每一分钟考虑两种情况
- 第
i
分钟没有任务:dp[i]=dp[i+1]
- 第
i
分钟有任务 a 1 , a 2 , a 3 , . . . a_1,a_2,a_3,... a1,a2,a3,...,则对于每一个结束于 t t t分钟的任务 a i a_i ai,更新dp[i]
:dp[i]=min(dp[i], dp[t+1]+t-i+1)
C++题解:
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr int N{10005};
int n,k;
int dp[N]; //dp[i]表示从第i分钟开始的最少工作时间
//最后答案是n-dp[1]
vector<int> st_to_end[N];
int main()
{
ios::sync_with_stdio(false);
cin >> n >> k;
memset(dp, 0x3f, sizeof dp);
for(int i=1;i<=k;i++)
{
int st, len; cin >> st >> len;
int en = st + len - 1;
st_to_end[st].push_back(en);
}
int qwq = n;
dp[n + 1] = 0;
for(int i=n;i>=0;i--)
{
if(st_to_end[i].empty()) //第i分钟没有任务
{
dp[i] = dp[i + 1];
}
else
{
for(auto t : st_to_end[i])
{
int len = t - i + 1;
dp[i] = min(dp[i], dp[t + 1] + len);
}
}
}
cout << n - dp[0] << endl;
return 0;
}
解法2
状态转移和上面一样,只不过可以用记忆化搜素,从前往后枚举,更符合思维习惯。
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr int N{10005};
int n,k;
int dp[N];
vector<int> st_to_end[N];
int dfs(int x)
{
if(dp[x]!=0x3f3f3f3f) return dp[x];
if(x > n) return dp[x] = 0;
if(st_to_end[x].size() == 0)
{
return dp[x] = dfs(x+1);
}
for(auto en : st_to_end[x])
{
dp[x] = min(dp[x], dfs(en+1) + en - x + 1);
}
return dp[x];
}
int main()
{
ios::sync_with_stdio(false);
cin >> n >> k;
memset(dp, 0x3f, sizeof dp);
for(int i=1;i<=k;i++)
{
int st, len; cin >> st >> len;
int en = st + len - 1;
st_to_end[st].push_back(en);
}
cout << n - dfs(0) << endl;
for(int i=0;i<=n;i++)
cout << dfs(i) << " ";
return 0;
}
GPA Calculator
难度:入门(1)
循环读入每一门课,用一个变量total_score
记录所有GP*学分
之和(GP
需要根据这门课的分数,用if-else计算),用另一个变量total_credit
记录所有学分
之和,最后total_score/total_credit
就是答案。
C++题解:
#include<bits/stdc++.h>
using namespace std;
double gpa = 0, total_score = 0, total_credit = 0;
int main()
{
int n; cin >> n;
for(int i=1;i<=n;i++)
{
int score;
double credit, gp;
cin >> score >> credit;
if(score >= 90)
gp = 4;
else if(score < 60)
gp = 0;
else
gp = 1 + (score - 60) * 0.1;
total_score += gp * credit;
total_credit += credit;
}
gpa = total_score / total_credit;
printf("%.2f", gpa);
return 0;
}
Python题解:
n = int(input())
total_credit = 0
total_score = 0
for i in range(n):
score, credit = map(float, input().split())
if score > 90:
gp = 4
elif score < 60:
gp = 0
else:
gp = 1 + (score - 60) * 0.1
total_credit += credit
total_score += credit * gp
gpa = total_score / total_credit
print("%.2f" % gpa)
Happy Birthday YNU
难度:入门(1)
用if-else判断年份 X X X,需要考虑5种情况:1921年及之前,1922年,1923年,1924年,1925年及之后。分别判断每一种情况做出相应输出即可。注意不要漏掉每一种情况。
C++题解:
#include<iostream>
#include<cstdio>
using namespace std;
int main() {
int n;
scanf("%d",&n);
n = n - 1923;
if(n >= 0) {
if(n == 0 || n == 1) printf("YNU is %d year old",n);
else printf("YNU is %d years old",n);
}
else {
if(n == -1) printf("YNU will be born after %d year",-n);
else printf("YNU will be born after %d years",-n);
}
}
Python题解:
x = int(input())
y = x - 1923
z = 1923 - x
if y >= 0:
if y >= 2:
print(f"YNU is {y} years old")
else:
print(f"YNU is {y} year old")
else:
if z >= 2:
print(f"YNU will be born after {z} years")
else:
print(f"YNU will be born after {z} year")