Codeforces Round 1013 (Div. 3)
2025-3-26 初版 A, B, C, D, E, G;
2025-3-27 补题 F;
反思
赛时 A B C D E,罚时 276,rank 5822;
\(\qquad\) 这一把其实不难,到了F,G才开始上强度,前面主要是D一开始没想到二分卡住了,然后E傻乎乎的给测试数据排了个序,debug了半天wa了5发。唉qwq,下次加油吧qwq
A-Olympiad Date
\(\qquad\) 这道题就是问在第几步中已经抽到的数字可以组成 2025-03-01.注意前导零,对每个数计数看什么时候全体大于等于所需数量就行。
#include <bits/stdc++.h>
using namespace std;
#define int long long
using ll = long long;
using ull = unsigned long long;
using uint = unsigned;
using pii = pair<int, int>;
void solve(){
int n;
cin >> n;
vector<int> a(n+1);
map<int, int> mp;
int ans = 1e9;
for(int i = 1; i <= n; i ++ ){
cin >> a[i];
mp[a[i]]++;
if(mp[0] >= 3 && mp[1] >= 1 && mp[2] >= 2 && mp[3] >= 1 && mp[5] >= 1){
ans = min(ans, i);
}
}
cout << (ans == 1e9 ? 0 : ans) << endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while(t--){
solve();
}
return 0;
}
B-Team Training
\(\qquad\) 这一题就是贪心,先把单个符合要求的拿出来加入答案里,然后对其他的进行排序每次选取最大的那个加入队伍中,当队伍人数×队伍最小值符合要求就加进答案里。
#include <bits/stdc++.h>
using namespace std;
#define int long long
using ll = long long;
using ull = unsigned long long;
using uint = unsigned;
using pii = pair<int, int>;
void solve(){
int n, x;
cin >> n >> x;
int ans = 0;
vector<int> a(n+1);
for(int i = 1; i <= n; i ++ ){
cin >> a[i];
if(a[i] >= x) ans++;
}
sort(a.begin()+1, a.end());
int len = 0, minn = 1e9;
for(int i = n-ans; i >= 1; i -- ){
minn = min(minn, a[i]);
len++;
if(minn*len >= x){
ans++;
len = 0;
}
}
cout << ans << endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while(t--){
solve();
}
return 0;
}
C-Combination Lock
\(\qquad\) 规律构造题,因为要求每次旋转都要有一个数字跟位置相同,而这一次相等于下一次相等的周期为 n,对此在草稿纸上简单列一下每个数字在上一轮可能从哪个位置转过来就可以发现,对于奇数列只需要倒序输出就可以得到答案,而对于偶数列不论怎么排列都无法符合题意。
#include <bits/stdc++.h>
using namespace std;
#define int long long
using ll = long long;
using ull = unsigned long long;
using uint = unsigned;
using pii = pair<int, int>;
void solve(){
int n;
cin >> n;
if(n%2 == 0) {cout << -1 << endl; return;}
for(int i = n; i >= 1; i -- ){
cout << i << ' ';
}
cout << endl;
return;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while(t--){
solve();
}
return 0;
}
D-Place of the Olympiad
\(\qquad\) 赛时的时候用了二分,就是二分查找当最大的长桌长度在总桌子数大于等于 k 的第一个数。后面看了一个学长的代码发现只要简单推一个式子就可以了QAQ.
#include <bits/stdc++.h>
using namespace std;
#define int long long
using ll = long long;
using ull = unsigned long long;
using uint = unsigned;
using pii = pair<int, int>;
void solve(){
int n, m, k;
cin >> n >> m >> k;
int l = 1, r = m;
while(l < r){
int mid = (l+r)/2;
int t = m/(mid+1);
int sum = t*mid + min(mid+1, m%(mid+1));
sum *= n;
if(sum >= k) r = mid;
else l = mid+1;
}
cout << l << endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while(t--){
solve();
}
return 0;
}
/* 下面是推式子的代码 */
// #include<bits/stdc++.h>
#include <iostream>
#include <set>
#include <cmath>
#include <algorithm>
#include <vector>
#include <map>
#include <queue>
#define int long long
using namespace std;
const int N = 5e5 + 10;
const int mod = 1e9 + 7;
int n, m, k;
int a[N];
string str;
void solve()
{
cin >> n >> m >> k;
int mm = k / n;
if(k % n) ++ mm;
// cout << mm << " ";
// if(m == mm) cout << "0\n";
// else
cout << (mm + (m - mm)) / (m - mm + 1) << "\n";
// cout << (((k + n - 1) / n) + (m - ((k + n - 1) / n)) - 1) / (m - ((k + n - 1) / n)) << "\n";
return;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int _T = 1;
cin >> _T;
while(_T --)
{
solve();
}
return 0;
}
E-Interesting Ratio
\(\qquad\) 最道心破碎的一题,思路全对就因为傻乎乎的给数据排了个序喜提罚时+80.
\(\qquad\) 这一题我们根据题目给出的公式 \(F(a, b) = lcm(a, b) / gcd(a, b)\) 然后对样例打个表就可以发现,对于 1-n 中的每一个质数 p,只要 \(i*p <= n\),就符合条件,因为此时 \(F(i, p) = lcm(i, p) / gcd(i, p)\) 即 \(F(i, p) = i*p / i = p\),此时 \(F(i, p)\) 一定为质数。所以我们只需要用线性筛筛出小于 n 的所有质数然后把每一个 n/p 计入答案即可。
#include <bits/stdc++.h>
using namespace std;
//#define int long long
using ll = long long;
using ull = unsigned long long;
using uint = unsigned;
using pii = pair<int, int>;
const int N= 10000100;
int primes[N], cnt;
bool st[N];
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) primes[cnt ++ ] = i;
for (int j = 0; primes[j] <= n / i; j ++ )
{
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
vector<int> n(t+1);
int maxx = -1;
for(int i = 1; i <= t; i ++ ) {cin >> n[i]; maxx = max(maxx, n[i]);}
get_primes(maxx);
for(int i = 1; i <= t; i ++ ){
int ans = 0;
for(int j = 0; j < cnt; j ++ ){
if(primes[j] > n[i]) break;
ans += n[i] / primes[j];
}
cout << ans << endl;
}
return 0;
}
F-Igor and Mountain
\(\qquad\) 补完啦!
\(\qquad\) 这道题我们考虑从底到顶的二维 \(dp\),因为有一个条件是,在一横排中最多使用两个占位符,因此进入这一排和离开这一排的点可能并不相同。在状态转移的同时我们还要记录这个点作为入点或者出点时的状态。我们用 \(dp[i][j][f]\) 来表示这个点的状态,\(i, j\) 为点的坐标,\(f\) 则为点的属性,当这个点作为入点,即从下层进入这一层的点时,我们定义 \(f == 0\),而当它作为出点时,定义 \(f == 1\).
\(\qquad\) 接着,我们来看状态转移的过程,当我们遍历到一点 \((i, j)\) ,且当它为一个入点时,我们可以由它延伸至两边可以攀爬的点作为出点,即 \([j-d, j+d]\) 范围内的有效点都可以作为它的出点。
\(\qquad\) 同理,当我们遍历到一个出点时,我们可以由他向下一层转移至可以触碰到的点作为入点,即 \([(i+1, j+sqrt(d*d-1)), (i+1, j-sqrt(d*d-1))]\) 范围内的点都可以作为下一层的入点并进行状态转移。
\(\qquad\) 我们发现当这样对符合要求的点进行状态转移时,时间复杂度过大,需要优化。而我们可以注意到,不论 \((i, j)\) 是作为入点对两边进行状态转移还是对下一层进行状态转移,我们都是对这一行中的一个区间进行操作,对此我们考虑使用差分进行优化。
#include <bits/stdc++.h>
using namespace std;
#define int long long
using ll = long long;
using ull = unsigned long long;
using uint = unsigned;
using pii = pair<int, int>;
const int MOD = 998244353;
const int N = 2222;
int dp[N][N][2];
string str[N];
void solve(){
int n, m, d;
cin >> n >> m >> d;
for(int i = 1; i <= n; i ++ ){
for(int j = 1; j <= m; j ++ ){
dp[i][j][0] = dp[i][j][1] = 0;
}
}
for(int i = n; i >= 1; i -- ){
string s;
cin >> s;
str[i] = "0" + s;
}
for(int j = 1; j <= m; j ++ ){
if(str[1][j] == 'X'){
dp[1][j][0] = 1;
}
}
for(int i = 1; i <= n; i ++ ){
for(int j = 1; j <= m; j ++ ){
if(str[i][j] == '#') continue;
int l = max(1LL, j-d), r = min(m, j+d);
dp[i][l][1] = (dp[i][l][1] + dp[i][j][0])%MOD;
dp[i][r+1][1] = (dp[i][r+1][1] - dp[i][j][0] + MOD)%MOD;
}
for(int j = 1; j <= m; j ++ ) dp[i][j][1] = (dp[i][j][1] + dp[i][j-1][1])%MOD;
if(i == n) continue;
for(int j = 1; j <= m; j ++ ){
int len = d*d-1;
len = sqrt(len);
int l = max(1LL, j-len), r = min(m, j+len);
if(str[i][j] == '#') continue;
dp[i+1][l][0] = (dp[i+1][l][0] + dp[i][j][1])%MOD;
dp[i+1][r+1][0] = (dp[i+1][r+1][0] - dp[i][j][1] + MOD)%MOD;
}
for(int j = 1; j <= m; j ++ ) dp[i+1][j][0] = (dp[i+1][j][0] + dp[i+1][j-1][0])%MOD;
}
ll ans = 0;
for(int j = 1; j <= m; j ++ ){
if(str[n][j] == 'X') ans = (ans + dp[n][j][1])%MOD;
}
cout << ans << endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while(t--){
solve();
}
return 0;
}
G-Gleb and Boating
\(\qquad\) 对于这一题首先可以特判一下,当 \(s\)%\(k == 0\) 的时候答案一定是 k. 然后我们来分析一下,因为不能超出 \([0, s]\) 的范围,那么如果第一次无法连续跳到 s 那么一定会经过两次回头,此时 k 就会变成 k-2.接着我们可以发现,每一次回跳 x 的时候,相较于再次转身在正轴上跳跃 x-1,我们会余下 1 的距离,也就是说,如果范围足够长,我们可以随意的控制第二次转身到终点 s 的距离。不难发现,当 \(s > k*k\) 时,k 在第一次转身回跳的次数一定大于等于 k 次,所以一定可以找到一个距离让第二次转身后可以连续跳跃到 s. 所以当 \(s > k*k\) 时,答案就是 \(max(1, k-2)\)。
\(\qquad\) 在判断完特殊情况之后,我们来看剩下的一般情况。分析一下转身的过程,我们发现,当转身次数为 0 或偶数次时,一定是面向正半轴的,而当转身次数为奇数次时,一定是面向负半轴的。而对于每次转身后的力量值 k 和位置 v,我们是不是能判断一下哪一些点是可以到达的。对此我们做一个动态规划,模拟力量值从 k 递减到 1 的过程中,f[i]代表是否能够到达这 i 点。如果在模拟的过程中某个时刻 \(f[s] == true\) 那么直接输出这个时刻的力量值即可。
#include <bits/stdc++.h>
using namespace std;
void solve(){
int s, k;
cin >> s >> k;
if(s%k == 0) {cout << k << endl; return;}
if(s > k*k) {cout << max(k-2, 1) << endl; return;}
vector<int> f(s+1);
f[0] = 1;
for(int i = k; i >= 1; i -- ){
if(((k-i)&1) == 0){
for(int x = s; x >= 0; x -- ){
f[x] = x >= i && f[x-i];
}
for(int x = i; x <= s; x ++ ){
f[x] = f[x] || f[x-i];
}
} else {
for(int x = 0; x <= s; x ++ ){
f[x] = x + i <= s && f[x+i];
}
for(int x = s-i; x >= 0; x -- ){
f[x] = f[x] || f[x+i];
}
}
if(f[s]) {cout << i << endl; return;}
}
cout << 1 << endl;
}
int main(){
int t;
cin >> t;
while(t--) solve();
return 0;
}