CSP-J/S 2024 Record / 终末游记
Day -2
真是自作自受啊。
逃避着,逃避着,一直逃避着。
最终,这一天还是近在咫尺了啊。
那我的实力提升了吗?
啊。
好想消失。
Day -1
\(252\) 分。
如果真的有这个分的话,我就不用这么窘迫了吧。
你骗人。
CSP-S 怎么可能有这么水的数据啊。
Day 0
\(250\) 分……
怎么可能……
要是挂分了,我会崩溃的啊。
命运将我打倒了两次。
让我成功一次吧。
Day 0.9 - 2024/10/26 23:53
想了一下,还是要写一下。
今年题目的平均难度显著低于去年。
然而不幸的是,由于作者在去年因为一个字符挂了大批分数,所以今年的分数估值是一个区间。
J: \([300,370]\),S: \([150,250]\)。
免责声明:这里出现的代码都是赛后使用家里的电脑重新实现的,不存在通过非法手段爆破压缩文件获得代码的可能。
J
实际上,由于前两年 J 都有一等,加上 J 的一等没有什么实质性作用,于是没有打起精神打。
T1 一眼望去直接使用 set
去重然后输出 set
的大小与 \(52\) 的差就过了。
时间复杂度 \(O(n\log n)\)。
#include <iostream>
#include <set>
#include <string>
using namespace std;
const int N = 1e5 + 10;
string s;
set<string> st;
int n;
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
cin >> s;
st.insert(s);
}
cout << 52 - st.size() << '\n';
}
T2 发现是 ABC 特有小清新模拟,直接打,一遍过。
时间复杂度 \(O(nm+k)\)。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
using namespace std;
const int N = 1e3 + 10;
int n, m, k, tx, ty, td, res;
string s[N];
bool vis[N][N];
const int dx[]{0, 1, 0, -1}, dy[]{1, 0, -1, 0};
void init_global()
{
}
void init_local()
{
scanf("%d%d%d", &n, &m, &k);
scanf("%d%d%d", &tx, &ty, &td);
tx--, ty--;
for (int i = 0; i < n; i++)
cin >> s[i], memset(vis[i], 0, m);
}
void run()
{
vis[tx][ty] = true;
for (int i = 0, nx = 0, ny = 0; i < k; i++)
{
nx = tx + dx[td], ny = ty + dy[td];
if (nx < 0 or nx >= n or ny < 0 or ny >= m or s[nx][ny] == 'x')
td = (td + 1) & 3;
else
tx = nx, ty = ny;
vis[tx][ty] = true;
}
res = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
res += vis[i][j];
}
printf("%d\n", res);
}
int main()
{
// freopen("explore.in", "r", stdin);
// freopen("explore.out", "w", stdout);
int T = 1;
scanf("%d", &T);
init_global();
while (T--)
{
init_local();
run();
}
}
T3 发现只给了小样例,顿感不妙,于是一顿分讨发现只有 \(0,2,4,6,7,8\) 这几个数字是有用的。然后发现 \(8\) 的密度最大,于是后面全部塞满 \(8\),然后对 \(n\bmod 7\) 的结果分类讨论,然后过了小样例。但这显然是不行的,因为样例不会这么弱。于是继续推,发现少判了 \(n\bmod 7=3\) 的情况,具体来说前面可以删一个 \(8\) 变成 \(22\) 或者删两个 \(8\) 变成 \(200\)。显然能删两个是最好的,然后过了。
时间复杂度 \(O(\sum n)\)。
#include <cstdio>
using namespace std;
const int N = 1e5 + 10;
int n, trs;
void init_global()
{
}
void init_local()
{
scanf("%d", &n);
}
void run()
{
if (n == 1)
return puts("-1"), void();
if (n == 3)
return puts("7"), void();
if (n == 4)
return puts("4"), void();
trs = n / 7;
n %= 7;
if (n == 1)
trs--, putchar('1'), putchar('0');
if (n == 2)
putchar('1');
if (n == 3)
{
if (trs >= 2)
putchar('2'), putchar('0'), putchar('0'), trs -= 2;
else
trs--, putchar('2'), putchar('2');
}
if (n == 4)
putchar('2'), putchar('0'), trs--;
if (n == 5)
putchar('2');
if (n == 6)
putchar('6');
for (int i = 0; i < trs; i++)
putchar('8');
putchar('\n');
}
int main()
{
int T = 1;
scanf("%d", &T);
init_global();
while (T--)
{
init_local();
run();
}
}
然后悲伤地发现不会 T4,于是在暴力建边的基础上加了一点小优化(比如说重复状态后停止枚举、尽可能缩小值域范围以及局数范围等等),于是过了大样例 \(1,2,3,5\),\(4\) RE,\(6\) TLE。
然后交到洛谷上,woc,\(70\) 分。
时间复杂度 \(O(\sum l+n\times \min(n,k)+\max(r)\times\max(S))\)。
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <utility>
#include <vector>
using namespace std;
const int N = 1e5 + 10, M = 2e5 + 10, K = 1e2 + 10;
int n, k, q, mx, mxv, fst[K][M], req;
bool flg;
using vi = vector<int>;
using pii = pair<int, int>;
vi road[N];
vector<pii> dsv[M];
pii qry[M];
template <typename _Tp> inline void read(_Tp &x)
{
char ch;
bool neg = false;
while (ch = getchar(), !isdigit(ch))
neg |= (ch == '-');
x = (ch ^ 48);
while (ch = getchar(), isdigit(ch))
x = (x << 3) + (x << 1) + (ch ^ 48);
x = neg ? -x : x;
}
template <typename _Tp, typename... _Args> inline void read(_Tp &x, _Args &...args)
{
read(x);
read(args...);
}
void init_global()
{
}
void init_local()
{
mx = mxv = 0;
read(n, k, q);
for (int i = 1, tp = 0; i <= n; i++)
{
read(tp);
road[i].assign(tp, 0);
for (auto &j : road[i])
read(j), mxv = max(mxv, j);
}
}
void run()
{
for (int i = 1; i <= q; i++)
{
read(qry[i].first, qry[i].second);
mx = max(mx, qry[i].first);
}
for (int i = 1; i <= mxv; i++)
dsv[i].clear();
memset(fst[0] + 1, -1, mxv << 2);
fst[0][1] = 0;
for (int i = 1; i <= n; i++)
{
for (int j = 0; j < road[i].size(); j++)
{
for (int tk = 1; tk < k and j + tk < road[i].size(); tk++)
{
dsv[road[i][j]].emplace_back(i, road[i][j + tk]);
}
}
}
for (int i = 1; i <= mx; i++)
{
memset(fst[i] + 1, -1, mxv << 2);
for (int j = 1; j <= mxv; j++)
{
if (fst[i - 1][j] == -1)
continue;
for (auto &tk : dsv[j])
{
if (tk.first == fst[i - 1][j] or !fst[i][tk.second])
continue;
if (fst[i][tk.second] == -1)
{
fst[i][tk.second] = tk.first;
continue;
}
if (fst[i][tk.second] == tk.first)
continue;
fst[i][tk.second] = 0;
}
}
flg = true;
for (int j = 1; j <= mxv; j++)
{
flg &= (fst[i][j] == fst[i - 1][j]);
if (!flg)
break;
}
if (flg)
{
mx = i;
break;
}
// for (int j = 1; j <= mxv; j++)
// printf("%d%c", fst[i][j], " \n"[j == mxv]);
}
for (int i = 1; i <= q; i++)
{
putchar(qry[i].second <= mxv and ~fst[min(qry[i].first, mx)][qry[i].second] ? '1' : '0');
putchar('\n');
}
}
int main()
{
int T = 1;
scanf("%d", &T);
init_global();
while (T--)
{
init_local();
run();
}
}
S
实际上,由于去年的单字符惨案过于惨烈,我已经不对我今年取得一等抱有任何希望了。当时到了赛场之后的唯一想法就是干完这票就退役,起码不要似得太难看。
于是考试开始了。
T1,显然是可以无脑排序然后线性推上去的,但是一开始过于紧张导致完全搞错了贪心策略,还好今年的大样例比较良心让我发现了这个错误。
时间复杂度 \(O(n\log n)\)。
#include <algorithm>
#include <cstdio>
using namespace std;
const int N = 1e5 + 10;
int n, ptr, a[N], res;
bool vis[N];
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", a + i);
}
sort(a + 1, a + n + 1);
for (int i = 1, ptr = 1; i <= n; i++)
{
if (a[ptr] >= a[i])
continue;
vis[ptr] = true;
ptr++;
}
for (int i = 1; i <= n; i++)
res += vis[i];
printf("%d\n", n - res);
}
T2,第一眼感觉是类似去年 T3 性质的大模拟,于是先去看 T3。
发现 T3 是 DP,于是马上放弃打正解,然后发现 \(O(n^2)\) 有 \(50\) 分就立刻开始想。然后第一次设计的 DP 状态很不幸地假了,过不了大样例。
于是在瞪了半个小时无果后回去看 T2,发现后面给的三个知识点纯纯诈骗,只有第三个是有用的,于是再一次仔细地看题(谢谢你,CSP-S 2023 T3,让我终于能够静下心来看题了)之后开打。
实际上这道题的实现难度并不高,关键在于弄清楚能查到超速的测速仪一定是一个区间,然后对于加速度正零负的情况跑不同的二分之后跑最小代价覆盖即可。
时间复杂度 \(O(n\log m+m)\)。
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <utility>
using namespace std;
const int N = 1e5 + 10, M = 1e6 + 10;
using ll = long long;
template <typename _Tp> inline void read(_Tp &x)
{
char ch;
bool neg = false;
while (ch = getchar(), !isdigit(ch))
neg |= (ch == '-');
x = (ch ^ 48);
while (ch = getchar(), isdigit(ch))
x = (x << 3) + (x << 1) + (ch ^ 48);
x = neg ? -x : x;
}
template <typename _Tp, typename... _Args> inline void read(_Tp &x, _Args &...args)
{
read(x);
read(args...);
}
int n, m, l, v, p[N], idx;
struct st
{
ll d, v, a;
} car[N];
using pii = pair<ll, ll>;
ll res1, res2, dp[M];
pii rg[N];
ll calc_v1sq(ll v0sq, ll acr, ll mov)
{
mov *= 2 * acr;
mov += v0sq;
return mov;
}
void init_global()
{
}
void init_local()
{
read(n, m, l, v);
v *= v;
for (int i = 1; i <= n; i++)
{
read(car[i].d, car[i].v, car[i].a);
}
for (int i = 1; i <= m; i++)
{
read(p[i]);
}
}
void run()
{
idx = res1 = res2 = 0;
for (int i = 1, tp = 0, tl = 0, tr = 0, mid = 0; i <= n; i++)
{
tp = lower_bound(p + 1, p + m + 1, car[i].d) - p;
if (tp > m)
continue;
if (car[i].a == 0)
{
if (car[i].v * car[i].v <= v)
continue;
res1++;
rg[++idx] = {m, tp};
continue;
}
if (car[i].a > 0)
{
tl = tp, tr = m + 1;
while (tl < tr)
{
mid = (tl + tr) >> 1;
if (calc_v1sq(car[i].v * car[i].v, car[i].a, p[mid] - car[i].d) <= v)
tl = mid + 1;
else
tr = mid;
}
if (tl > m)
continue;
res1++;
rg[++idx] = {m, tl};
continue;
}
tl = tp - 1, tr = m;
while (tl < tr - 1)
{
mid = (tl + tr) >> 1;
if (calc_v1sq(car[i].v * car[i].v, car[i].a, p[mid] - car[i].d) <= v)
tr = mid - 1;
else
tl = mid;
}
if (calc_v1sq(car[i].v * car[i].v, car[i].a, p[tr] - car[i].d) <= v)
tr = tl;
else
tl = tr;
if (tl < tp)
continue;
res1++;
rg[++idx] = {tl, tp};
}
sort(rg + 1, rg + idx + 1);
for (int i = 1, pidx = 0; i <= idx; i++)
{
// printf("%lld %lld\n", rg[i].first, rg[i].second);
while (pidx < rg[i].first)
pidx++, dp[pidx] = dp[pidx - 1];
dp[pidx] = max(dp[pidx], dp[rg[i].second - 1] + 1);
res2 = max(res2, dp[pidx]);
}
printf("%lld %lld\n", res1, m - res2);
}
int main()
{
// freopen("detect.in", "r", stdin);
// freopen("detect.out", "w", stdout);
int T = 1;
scanf("%d", &T);
init_global();
while (T--)
{
init_local();
run();
}
}
实际上过掉大样例之后我还觉得自己是在做梦,于是拼命地检查自己的前两题代码有没有错误,在赛场紧张的环境下自然是没有发现的。这也正是我怀疑这题可能会爆零的原因。
于是终于可以拿出时间来检查 T3 的 DP 状态设计。于是脑子里一阵 yy 之后十分幸运地找到了原来 DP 状态的问题,改为 \(d_{i,j}\) 表示以第 \(i\) 个位置为结尾且尾部同色极大连通块大小为 \(j\) 的最大答案就可以通过了。
于是这个时候大样例的良心又体现出来了。当时我发现可以通过滚动数组优化空间,于是就改了写法,在 memset
里面手动指定了初始化的大小。然后成功地把大样例 \(2\) 跑了起来,\(2\) 分钟后运行完成,发现有一个输出和答案不一样,于是我条件反射似的去找 memset
,问题果然出在那里,i<<2
应该改为 i<<3
。然后改好了,再跑一遍,\(1.5\) 分钟后,运行结束,答案对了。
于是果断放掉这道题。
时间复杂度 \(O(n^2)\)。
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 2e5 + 10;
using ll = long long;
int n, a[N];
ll dp[2][N], res;
template <typename _Tp> inline void read(_Tp &x)
{
char ch;
bool neg = false;
while (ch = getchar(), !isdigit(ch))
neg |= (ch == '-');
x = (ch ^ 48);
while (ch = getchar(), isdigit(ch))
x = (x << 3) + (x << 1) + (ch ^ 48);
x = neg ? -x : x;
}
template <typename _Tp, typename... _Args> inline void read(_Tp &x, _Args &...args)
{
read(x);
read(args...);
}
void init_global()
{
}
void init_local()
{
read(n);
for (int i = 1; i <= n; i++)
{
read(a[i]);
}
}
void run()
{
for (int i = 1; i <= n; i++)
{
memset(dp[i & 1] + 1, 0, i << 3);
for (int j = 1; j < i; j++)
{
dp[i & 1][1] = max(dp[i & 1][1], dp[i & 1 ^ 1][j] + (a[i] == a[i - j - 1] ? a[i] : 0));
}
for (int j = 2; j <= i; j++)
{
dp[i & 1][j] = max(dp[i & 1][j], dp[i & 1 ^ 1][j - 1] + (a[i] == a[i - 1] ? a[i] : 0));
}
}
res = 0;
for (int i = 1; i <= n; i++)
{
res = max(res, dp[n & 1][i]);
}
printf("%lld\n", res);
}
int main()
{
int T = 1;
scanf("%d", &T);
init_global();
while (T--)
{
init_local();
run();
}
}
接下来,距离比赛结束仅剩一个小时,我尝试着打 T4 的模拟,结果十分不幸地没有调出来,喜提 \(0\) 分。
这就是今年这戏剧一般的旅程。
总结
其实,本来我以为今年我超常发挥了。但是看到旁边通宵的同学都在为拿到 \(300+\) 分而庆祝的时候,一股恐怖的寒意还是在我的心中出现了。我十分地惶恐,惶恐于我已经跟不上其他人的队伍了。但是我又庆幸,庆幸我终于能够静下心来设计 DP 状态了。
当然,在从考场回家的路上,我的庆幸完全消耗殆尽了。我感到由衷的自卑,因为按照他们(也就是我的队友们)所说的分数,我无疑只能排在最后。但,考试向来如此。
唯有奔跑才能留在原地。我奔跑了,但不够快——后面追赶我的人如潮涌般袭来。我还需要更快。
在接下来的一周内,忘掉它吧。
该从文化课中汲取一些之前缺少的知识了。
Day 1.5 - 2024/10/27 13:48
把 T4 的暴力实现出来了,\(48\) 分。
太可惜了,如果在场上实现出来的话,就算 T2 挂掉也不会没有一等了。
具体思路:直接模拟淘汰过程,与完全暴力枚举胜负不同,这里对阵双方以群组的方式存在,具体地说,若擂主一方的群组内存在能力值未定或能力值不符合条件的选手则非擂主一方全部存活,并对擂主一方群组内的胜负可能分别判断并合并。否则,非擂主一方全部失败。
时间复杂度:\(O(Tnm)\)。
#include <cctype>
#include <cstdio>
#include <vector>
using namespace std;
const int N = 1e5 + 10;
int n, m, a[N], x[4], sht[20][N], idx, qry[N], pid = 1, tcnt, tpid, ttcnt;
vector<int> vec[N << 1];
using ll = long long;
ll res;
template <typename _Tp> inline void read(_Tp &x)
{
char ch;
while (ch = getchar(), !isdigit(ch) and ~ch)
;
x = (ch ^ 48);
while (ch = getchar(), isdigit(ch))
x = (x << 3) + (x << 1) + (ch ^ 48);
}
template <typename _Tp, typename... _Args> inline void read(_Tp &x, _Args &...args)
{
read(x);
read(args...);
}
template <typename _Tp> inline void read_single(_Tp &x)
{
char ch;
while (ch = getchar(), !isdigit(ch) and ~ch)
;
x = (ch ^ 48);
}
void init_global()
{
read(n, m);
for (int i = 1; i <= n; i++)
{
read(a[i]);
}
for (int i = 1; i <= m; i++)
{
read(qry[i]);
}
while (pid < n)
pid <<= 1, tcnt++;
for (int i = 0; i < tcnt; i++)
for (int j = 0; j < (1 << (tcnt - i - 1)); j++)
read_single(sht[i][j]);
}
void init_local()
{
for (int i = 0; i < 4; i++)
read(x[i]);
for (int i = 1; i <= n; i++)
a[i] ^= x[i & 3];
}
ll proc_query(int va)
{
bool not_fail = false;
tpid = 1, ttcnt = 0;
while (tpid < va)
tpid <<= 1, ttcnt++;
for (int i = 0; i < tpid; i++)
vec[i].assign(1, i + 1);
for (int i = 0; i < ttcnt; i++)
{
for (int j = 0, tj = 0, opr2 = 0; j < tpid; j += (1 << (i + 1)), tj++)
{
opr2 = j + (1 << i);
if (!sht[i][tj])
{
not_fail = true;
for (auto &trk : vec[j])
{
not_fail &= (trk <= va and a[trk] > i);
if (!not_fail)
break;
}
if (not_fail)
{
vec[opr2].clear();
continue;
}
for (auto &trk : vec[j])
{
if (trk > va or a[trk] > i)
vec[opr2].emplace_back(trk);
}
vec[j].swap(vec[opr2]);
vec[opr2].clear();
continue;
}
not_fail = true;
for (auto &trk : vec[opr2])
{
not_fail &= (trk <= va and a[trk] > i);
if (!not_fail)
break;
}
if (not_fail)
{
vec[j].swap(vec[opr2]);
vec[opr2].clear();
continue;
}
for (auto &trk : vec[opr2])
{
if (trk > va or a[trk] > i)
vec[j].emplace_back(trk);
}
// vec[j].swap(vec[opr2]);
vec[opr2].clear();
}
}
ll tres = 0;
for (auto &i : vec[0])
tres += i;
return tres;
}
void run()
{
res = 0;
for (int i = 1; i <= m; i++)
res ^= i * proc_query(qry[i]);
printf("%lld\n", res);
for (int i = 1; i <= n; i++)
a[i] ^= x[i & 3];
}
int main()
{
int T = 1;
init_global();
read(T);
while (T--)
{
init_local();
run();
}
}
Day 9.75 - 2024/11/4 17:23
我深知,那一刻要到了。
想到这句话的时候,我紧盯着电脑屏幕。
公布成绩的时间,从明天的中午,调到今天的中午,又变成了 17:30。
就在刚考完化学的时候,我还在想象着,又一个意想不到的低分对我大声嘲讽的场景。
我啊。
到底,进步了吗?
Day 9.9 - 2024/11/4 19:46
这是一段故事的结束,另一段故事的开始。
我曾经无数次预演的 bad 线最终没有被合并到现实。
最终分数:
J \(100+100+100+70=370\)
S \(100+100+50+20=270\)
至少在今年,我没有挂分。
把自己在赛时写的注释拿出来纪念一下吧。
J-T1
// 8:41 passed all samples.
J-T2
// 8:54 passed all samples.
J-T3
// 9:16 passed small example.
// 9:23 passed hack sample #1.
// 11:20 passed hack sample #2.
J-T4
/*
brute force.
10:55 passed sample #1,#2,#3,#5. Also got correct answer on #6 but TLE. RE on #4.
Assume being possible to get 35 points.
11:08 applied a simple optimization, didn't change the overall time complexity,
#4 RE, #6 TLE, but decreased the time usage on #6 by 4/5, #5 by 1/2.
Assume being able to get 35 points.
*/
S-T1
/*
14:42 passed all samples.
*/
S-T2
/*
16:24 passed all samples.
17:36 in order to avoid possible degrade, changed all int to long long.
*/
S-T3
/*
16:42 found the dp design was wrong.
16:47 redesigned the dp and successfully captured the part of n<=2000. Assume possible to get 50 points.
16:50 optimized the memory allocation. Maybe able to capture some extra points.
16:54 found out a critical mistake by making the process of larger n possible. Thanks for the strong samples.
16:58 decreased the constant factor by calculating the needed space for memset.
17:23 linear solution failed.
18:01 final check for sample 2: 1m36.076s.
*/
S-T4
// 17:57 failed. Maximum possible points: 250.