本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-03-25 16:19:54
不需要提交到洛谷,简单说说。
另外我就说一下我的垃圾做法。
后续补上 ly 神仙做法。
题解
树剖板子。
详解见 oiwiki。
关于怎么将边权转化为点权:
每个点的点权是它到父节点的边的权值。
结束了。
本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-03-25 16:19:54
不需要提交到洛谷,简单说说。
另外我就说一下我的垃圾做法。
后续补上 ly 神仙做法。
树剖板子。
详解见 oiwiki。
关于怎么将边权转化为点权:
每个点的点权是它到父节点的边的权值。
结束了。
本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-03-27 00:05:10
没进金组。
水题,不说了。
等会了再写。
等会了再写。
本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-04-05 01:04:50
虽然掉了 rating,不过还是感谢比赛找出我的弱点,upvoted。
1.5h 的时候只做出来 A,当时都快崩溃了,但是接下来 0.5h 连续干掉 3 题,成功保住青名。
跳过。
B 半天没调出来是因为看错了一个条件。
这个从第一位开始贪心即可。
一个 trick:
$fib_n \cdot fib_{n+1} = \sum_{i=0}^{n} fib_i^2$。
对着样例解释的图看看。
发现从大到小填充就行了。
没有 $4$,可以暂时看成 $9$ 进制数,由于实际上没有 $4$ 而不是没有 $9$ ,需要再把答案每一位,如果这一位大于等于 $4$,那么加一。
等我看题解
设状态 $dp_{i,j}$ 代表第 $i$ 位,是路径的第 $j$ 个的方案数。
然后 $O(n^2)$ 转移就行了。
至于求出最长路径长度,只需要提前 dp 一次,看最长是多少,使得方案数不等于 $0$。
注意 G2 第 $30$ 个测试点答案是 $10^9+7$ 的倍数,这个地方要注意一下。
本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-04-07 01:17:36
可能 OI 时间会被 whk 占光,考虑到这一点,趁着家长不在,写多少是多少。
略
设最大延伸到的长度为 $i$,那么显然答案为 $i-1 + ceil (\frac{a}{i}) + ceil(\frac{b}{i})$。
而简单计算可得只需要将 $i$ 枚举到大约 $\sqrt{10^9}$ 就行了,我枚举到 1e5。
简单贪心。
暂时不会
暂时不会
暂时不会。
本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-04-09 12:10:21
纪念一下第一次不看题解,不看算法标签做出 CF 评分 2200。
虽然实际上最多提高组 T1。
G2 做法只需要在 G1 基础上稍微修改。
很自然想到设 $dp_{i,j}$ 表示第 $i$ 位是路径的第 $j$ 个点的方案数。
想一下如何转移,对于 $i = 1$,显然方案数是 $1$。
对于 $i \ge 2$,分一下类:
主要思想就是这个。
关于怎么统计之前符合某些条件的状态答案综合,这个开个数组记一下就行了,都把 dp 设计出来了,这么简单的想必都会。
剩下的就是从大到小枚举 $m$,每次跑一遍 dp,找到答案就终止。
dp 复杂度 $O(n^2)$。
枚举最劣复杂度 $O(n)$。
总复杂度 $O(n^3)$。
dp 不大可能优化了,看枚举过程很方便优化。
主要就是优化寻找最长路径长度的过程。
我们可以先做一遍 dp,看路径长度最高到多少,使得答案不为 $0$。
然后对这个路径长度 dp 一下就行了。
UPD:做法麻烦了,实际上直接 dp 一遍就行了。
\/\/ LUOGU_RID: 107120197
#include <bits\/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = a; i <= b; i++)
#define REP(i, a, b) for (int i = a; i >= b; i--)
#define pb push_back()
#define mkpr make_pair
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int maxn = 5005;
typedef long long ll;
const ll mod = 1e9 + 7;
int t;
int n, k;
int c[maxn];
ll dp[maxn][maxn]; \/\/ 第 i 位状态为 j
ll sum[maxn][maxn]; \/\/ 颜色为 i,状态为 j,dp 值和
ll sum2[maxn]; \/\/ 状态为 i dp 总和
bool dp2[maxn][maxn],sum21[maxn][maxn],sum22[maxn];
inline int turn(int status)
{
if (status % k == 0)
return k;
return status % k;
}
int main()
{
scanf("%d", &t);
while (t--)
{
scanf("%d%d", &n, &k);
FOR(i, 1, n)
{
scanf("%d", &c[i]);
}
ll ans = 0;
FOR(i, 1, n)
{
dp2[i][1]=1;
FOR(j, 2, n)
{
if (turn(j) == 1)
{
dp2[i][j]|=sum22[j-1];
}
else
{
dp2[i][j]|=sum21[c[i]][j-1];
}
}
FOR(j, 1, n)
{
sum22[j] |= dp2[i][j];
sum21[c[i]][j] |= dp2[i][j];
}
}
int blocks = n \/ k;
bool isansoutputed=0;
for (int m = blocks * k; m > 0; m -= k)
{
bool flag = 0;
FOR(i, 1, n)
{
if (dp2[i][m])
{
flag = 1;
break;
}
}
if (flag)
{
\/\/ printf("m = %d\n", m);
FOR(i, 1, n)
{
dp[i][1] = 1;
FOR(j, 2, m)
{
if (turn(j) == 1)
{
dp[i][j] += sum2[j - 1];
}
else
{
dp[i][j] += sum[c[i]][j - 1];
}
dp[i][j] %= mod;
}
FOR(j, 1, m)
{
sum2[j] += dp[i][j];
sum2[j]%=mod;
sum[c[i]][j] += dp[i][j];
sum[c[i]][j] %= mod;
}
}
FOR(i, 1, n)
{
ans += dp[i][m];
ans%=mod;
FOR(j, 1, n)
{
\/\/ printf("dp[%d][%d] = %d\n",i,j,dp[i][j]);
dp[i][j] = 0;
sum[i][j] = 0;
}
sum2[i] = 0;
}
isansoutputed=1;
break;
}
}
if (!isansoutputed)
ans++;
printf("%lld\n", ans % mod);
FOR(i, 1, n)
{
sum2[i] = sum22[i]=0;
FOR(j, 1, n)
{
dp[i][j] = dp2[i][j]=0;
sum[i][j] = sum21[i][j]=0;
}
}
}
return 0;
}
本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-04-09 12:13:23
纪念一下第一次不看题解,不看算法标签做出 CF 评分 2200。
虽然实际上最多提高组 T1。
G2 做法只需要在 G1 基础上稍微修改。
很自然想到设 $dp_{i,j}$ 表示第 $i$ 位是路径的第 $j$ 个点的方案数。
想一下如何转移,对于 $i = 1$,显然方案数是 $1$。
对于 $i \ge 2$,分一下类:
主要思想就是这个。
关于怎么统计之前符合某些条件的状态答案综合,这个开个数组记一下就行了,都把 dp 设计出来了,这么简单的想必都会。
剩下的就是从大到小枚举 $m$,每次跑一遍 dp,找到答案就终止。
dp 复杂度 $O(n^2)$。
枚举最劣复杂度 $O(n)$。
总复杂度 $O(n^3)$。
\/\/ LUOGU_RID: 107120197
#include <bits\/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = a; i <= b; i++)
#define REP(i, a, b) for (int i = a; i >= b; i--)
#define pb push_back()
#define mkpr make_pair
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int maxn = 5005;
typedef long long ll;
const ll mod = 1e9 + 7;
int t;
int n, k;
int c[maxn];
ll dp[maxn][maxn]; \/\/ 第 i 位状态为 j
ll sum[maxn][maxn]; \/\/ 颜色为 i,状态为 j,dp 值和
ll sum2[maxn]; \/\/ 状态为 i dp 总和
bool dp2[maxn][maxn],sum21[maxn][maxn],sum22[maxn];
inline int turn(int status)
{
if (status % k == 0)
return k;
return status % k;
}
int main()
{
scanf("%d", &t);
while (t--)
{
scanf("%d%d", &n, &k);
FOR(i, 1, n)
{
scanf("%d", &c[i]);
}
ll ans = 0;
FOR(i, 1, n)
{
dp2[i][1]=1;
FOR(j, 2, n)
{
if (turn(j) == 1)
{
dp2[i][j]|=sum22[j-1];
}
else
{
dp2[i][j]|=sum21[c[i]][j-1];
}
}
FOR(j, 1, n)
{
sum22[j] |= dp2[i][j];
sum21[c[i]][j] |= dp2[i][j];
}
}
int blocks = n \/ k;
bool isansoutputed=0;
for (int m = blocks * k; m > 0; m -= k)
{
bool flag = 0;
FOR(i, 1, n)
{
if (dp2[i][m])
{
flag = 1;
break;
}
}
if (flag)
{
\/\/ printf("m = %d\n", m);
FOR(i, 1, n)
{
dp[i][1] = 1;
FOR(j, 2, m)
{
if (turn(j) == 1)
{
dp[i][j] += sum2[j - 1];
}
else
{
dp[i][j] += sum[c[i]][j - 1];
}
dp[i][j] %= mod;
}
FOR(j, 1, m)
{
sum2[j] += dp[i][j];
sum2[j]%=mod;
sum[c[i]][j] += dp[i][j];
sum[c[i]][j] %= mod;
}
}
FOR(i, 1, n)
{
ans += dp[i][m];
ans%=mod;
FOR(j, 1, n)
{
\/\/ printf("dp[%d][%d] = %d\n",i,j,dp[i][j]);
dp[i][j] = 0;
sum[i][j] = 0;
}
sum2[i] = 0;
}
isansoutputed=1;
break;
}
}
if (!isansoutputed)
ans++;
printf("%lld\n", ans % mod);
FOR(i, 1, n)
{
sum2[i] = sum22[i]=0;
FOR(j, 1, n)
{
dp[i][j] = dp2[i][j]=0;
sum[i][j] = sum21[i][j]=0;
}
}
}
return 0;
}
本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-04-16 11:53:20
居然没有扫描线题解。
扫描线远远快于二维前缀和,我的二维前缀和是 CF 上最劣解,而扫描线是最优解。
可以二分答案。
设当前二分到的时间是 $t$。
考虑如何判定。
显然被火烧过区域由一个个矩形组成。
而我们的主要任务是求出没被矩形覆盖的区域能不能通过再添加一个边长为 $2t+1$ 的矩形解决,即找到没覆盖点的纵坐标最大值,纵坐标最小值,横坐标最大值,横坐标最小值。
hy
考虑通过扫描线解决。
把所有的矩形分成两个竖线,第一个竖线代表这个矩形出现,第二个竖线代表这个矩形消失。
坐标虽然很大,把纵坐标离散化一下就解决了。
然后把所有竖线按照升序排列,依次处理。
如果第一条竖线横坐标大于 $1$,那么没覆盖纵坐标最大值和最小值直接确定为 $n$ 和 $1$,没覆盖最大最小横坐标对应更新一下。
每处理一条竖线,就利用线段树修改这条竖线对应区间,用当前最高和最低没覆盖点进行更新,如果这条竖线存在点没覆盖,那么还可以更新横坐标。
如果最后一条竖线后面有空的,再处理一下就行了。
关于线段树细节,树上每个点存储对应区间最高和最低没覆盖的点,然后常规维护就行了。
注意离散化如果不把 $1$ 和 $n$ 也离散化,会 Wrong answer on test10。
#include <bits\/stdc++.h>
using namespace std;
typedef long long ll;
#define FOR(i, a, b) for (int i = a; i <= b; i++)
#define REP(i, a, b) for (int i = a; i >= b; i--)
const int maxn = 3005;
int n, m, k;
struct Node
{
int x, y;
\/\/ 横坐标,纵坐标
} node[maxn];
ll lsh[maxn * 2];
int lshtop;
struct Line
{\/\/ 存储竖线信息
ll x, y1, y2, mark;
\/\/ y1 是这条竖线的上端点,y2 是下端点
} line[maxn * 2];
int linetop;
ll sgtcnt[maxn << 3], L[maxn << 3], R[maxn << 3], maxup[maxn << 3], mindw[maxn << 3];
\/\/ maxup 是最高的没覆盖点,min 是最低的没覆盖,注意这个是离散化之后的值
\/\/ sgtcnt 是当前区间被完全覆盖的次数
void build(int pos, int l, int r)
{
sgtcnt[pos] = 0;
L[pos] = l;
R[pos] = r;
maxup[pos] = r;
mindw[pos] = l;
if (l == r)
{
return;
}
int mid = (l + r) >> 1;
build(pos << 1, l, mid);
build(pos << 1 | 1, mid + 1, r);
}
void pushup(int now)
{
if (sgtcnt[now])
{
maxup[now] = -5e9;
mindw[now] = 5e9;
}
else
{
if (L[now] != R[now])
{
maxup[now] = max(maxup[now << 1], maxup[now << 1 | 1]);
mindw[now] = min(mindw[now << 1], mindw[now << 1 | 1]);
}
else
{
maxup[now] = mindw[now] = L[now];
}
}
}
void change(int now, int l, int r, int val)
{
if (l <= L[now] && R[now] <= r)
{
sgtcnt[now] += val;
pushup(now);
return;
}
if (L[now] > r || R[now] < l)
return;
int mid = (L[now] + R[now]) >> 1;
if (mid >= l)
change(now << 1, l, r, val);
if (mid < r)
change(now << 1 | 1, l, r, val);
pushup(now);
}
bool check(ll time)
{
lshtop = 0, linetop = 0;
FOR(i, 1, k)
{
lsh[++lshtop] = min(1ll * n, node[i].y + time);
lsh[++lshtop] = max(1ll, node[i].y - time);
lsh[++lshtop] = min(1ll * n, node[i].y + time + 1);
lsh[++lshtop] = max(1ll, node[i].y - time - 1);
lsh[++lshtop] = min(1ll * n, max(1ll,node[i].y + time - 1));
lsh[++lshtop] = max(1ll, min(1ll*n,node[i].y - time + 1));
}
lsh[++lshtop] = 1;
lsh[++lshtop] = n;
sort(lsh + 1, lsh + lshtop + 1);
lshtop = unique(lsh + 1, lsh + lshtop + 1) - lsh - 1;
\/\/ 离散化
FOR(i, 1, k)
{
int nwy1 = lower_bound(lsh + 1, lsh + lshtop + 1, min(1ll * n, node[i].y + time)) - lsh;
int nwy2 = lower_bound(lsh + 1, lsh + lshtop + 1, max(1ll, node[i].y - time)) - lsh;
\/\/ 计算离散化之后的 y
line[++linetop] = {max(1ll, node[i].x - time), nwy1, nwy2, 1};
line[++linetop] = {min(1ll * m, node[i].x + time) + 1, nwy1, nwy2, -1};
\/\/ 竖线
\/\/ 第二条线代表删除这个矩形
}
sort(line + 1, line + linetop + 1, [&](Line &a, Line &b)
{ if(a.x!=b.x)
return a.x < b.x;
else
return a.mark>b.mark; });\/\/ 对竖线按横坐标升序排列。
build(1, 1, lshtop);\/\/ 建树
ll maxupf = -5e9, mindwf = 5e9, maxrigf = -5e9, minleff = 5e9;
if (line[1].x > 1)
{\/\/ 前面有空的区域
maxupf = n;
mindwf = 1;
minleff = 1;
maxrigf = line[1].x - 1;
\/\/ puts("Updated\n");
}
int cnt = 0;
int posx=0;
ll before_x = 0;
FOR(i, 1, linetop)
{\/\/ 运行扫描线
if (line[i].x > m)
break;
change(1, line[i].y2, line[i].y1, line[i].mark);
cnt++; \/\/ 计算到目前为止还有多少竖线没处理
posx=line[i].x;
if (line[i].x != line[i + 1].x)
{
cnt = 0;
if (mindw[1] != 5e9)
{
maxupf = max(maxupf, lsh[maxup[1]]);
mindwf = min(mindwf, lsh[mindw[1]]);
maxrigf = max(maxrigf, line[i + 1].x - 1);
minleff = min(minleff, before_x + 1);
}
before_x = line[i].x;
}
}
if (line[linetop].x < m)
{\/\/ 后面有空的区域
maxupf = n;
mindwf = 1;
maxrigf = m;
minleff = min(minleff, line[linetop].x + 1);
}
return ((maxupf - mindwf + 1 <= time * 2 + 1) && (maxrigf - minleff + 1 <= time * 2 +1));
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
FOR(i, 1, k)
{
scanf("%d%d", &node[i].x, &node[i].y);
swap(node[i].x, node[i].y);
}
int l = 0, r = 1e9;
while (l < r)
{
int mid = (l + r) >> 1;
if (check(mid))
{
r = mid;
}
else
{
l = mid + 1;
}
}
printf("%d", l);
return 0;
}
本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-05-04 22:15:26
支持 Special Judge,直接通过源代码对拍,自定义比较器。
另外鉴于 Linux 自带的 diff 不能处理行空格和文末换行,我自己写了个比较器。
所有功能组合都测试过了一遍。
欢迎大佬们看蒟蒻有没有写挂。
帮助写在了注释里面。
对拍器:
\/*
这个对拍器支持 SPJ,支持直接通过源代码对拍,支持自由选择文件比较器。
这些选项都是可以自由切换的,对拍器内可以选择 全文比较\/Special Judge,源代码对拍\/可执行文件对拍,自由选择文件比较器。
- 对于 Special Judge 对拍
本程序将执行:<SpecialJudge> <input> <Output> <answer>
即和 testlib 的 checker 命令格式一样。
SPJ 返回代码为 0,代表正确,否则错误。
*\/
#include <bits\/stdc++.h>
using namespace std;
int main()
{
cout<<"==============对拍程序=============\n";
string stdname,bruname,genname,spjname;
string stdexe,bruexe,genexe,spjexe;
int time_now=clock();
int op,op2;
cout<<"待对拍程序以源文件给出,请输入 0,以可执行文件给出,请输入 1:";
cin>>op;
if(op!=0&&op!=1)
{
cerr<<"不合法\n";
exit(0);
}
cout<< "若使用 Special Judge,输入 1,否则输入 0:";
cin>>op2;
if(op2!=0&&op2!=1)
{
cerr<<"不合法\n";
exit(0);
}
if(op==0)
{
cout<<"待对拍程序 1 源文件:";
cin>>stdname;
cout<<"待对拍程序 2 源文件:";
cin>>bruname;
cout<<"输入数据生成器源文件:";
cin>>genname;
if(op2==1)
{
cout<<"请输入 Special Judge 源文件:";
cin>>spjname;
}
stdexe=stdname+to_string(time_now);
bruexe=bruname+to_string(time_now+1);
genexe=genname+to_string(time_now+2);
spjexe=spjname+to_string(time_now+3);
string compile_std="g++ "+stdname+" -o "+stdexe+" -std=c++14";
string compile_bru="g++ "+bruname+" -o "+bruexe+" -std=c++14";
string compile_gen="g++ "+genname+" -o "+genexe+" -std=c++14";
string compile_spj="g++ "+spjname+" -o "+spjexe+" -std=c++14";
system(compile_std.c_str());
system(compile_bru.c_str());
system(compile_gen.c_str());
if(op2==1)
{
system(compile_spj.c_str());
}
}
else
{
cout<<"待对拍程序 1 可执行文件:";
cin>>stdexe;
cout<<"待对拍程序 2 可执行文件:";
cin>>bruexe;
cout<<"输入数据生成器可执行文件:";
cin>>genexe;
if(op2==1)
{
cout<<"请输入 Special Judge 可执行程序\n";
cin>>spjexe;
}
}
string dataname=to_string(time_now)+to_string(random_device{}());
string in=dataname+".in";
string std_out=dataname+".out_std";
string bru_out=dataname+".out_bru";
string makeinput=genexe+" > "+in;
string execstd=stdexe+" < "+in+" > "+std_out;
string execbru=bruexe+" < "+in+" > "+bru_out;
string execspj=spjexe+" "+in+" "+std_out+" "+bru_out+" > "+dataname+".spj";
string chkdiff;
string execchkdif;
if(op2==0)
{
cout<<"请输入文件比较器可执行程序:";
cin>>chkdiff;
}
execchkdif=chkdiff+" "+std_out+" "+bru_out+" > "+dataname+".dif";
int testcnt=0;
while(1)
{
testcnt++;
system(makeinput.c_str());
system(execstd.c_str());
system(execbru.c_str());
if(op2==1)
{
cout<<"Test#"<<testcnt<<" verdict: ";
if(system(execspj.c_str())==0)
{
cout<<"OK\n";
}
else
{
cout<<"Wrong answer\n";
break;
}
}
else
{
cout<<"Test#"<<testcnt<<" verdict: ";
if(system(execchkdif.c_str())==0)
{
cout<<"OK\n";
}
else
{
cout<<"Wrong answer\n";
break;
}
}
sleep(1);
}
return 0;
}
文件比较器:
\/*
比较两个文件,忽略行末空格,文末空格,制表符,换行。
用法:
- 帮助
file-diff --help
- 比较
file-diff <file1> <file2>
- 返回值
若命令行参数错误,返回代码 -2
若在比较模式下,文件不同,返回 -1
否则返回 0,代表正常
*\/
#pragma GCC optimize("O2")
#include <bits\/stdc++.h>
using namespace std;
string help="比较两个文件,忽略行末空格,文末空格,制表符,换行。\n \
用法: \n\
- 帮助 \n\
file-diff --help \n\
- 比较 \n\
file-diff <file1> <file2> \n\
- 返回值 \n\
若命令行参数错误,返回代码 -2 \n\
若在比较模式下,文件不同,返回 -1 \n\
否则返回 0,代表正常 \n\
*\/";
bool check(string& s)
{
if(s.empty())return 1;
for(char v:s)
{
if(v!=' '&&v!='\n'&&v!='\r'&&v!='\t')return 0;
}
return 1;
}
int main(int argc,char* argv[])
{
if(argc==1)
{
cerr << "没有输入命令行参数,通过 --help 获取帮助\n";
return -2;
}
if(argc==2)
{
if(string(argv[1])=="--help")
{
cout<<help;
return 0;
}
else
{
cerr << "未知命令\n";
return -2;
}
}
else
{
if(argc!=3)
{
cerr << "命令行参数数量错误\n";
return -2;
}
else
{
string filename1=argv[1];
string filename2=argv[2];
auto file1=ifstream(filename1);
auto file2=ifstream(filename2);
vector<string> get1,get2;
string temp;
while(getline(file1,temp))
{
for(size_t idx=temp.size()-1;idx>=0;idx--)
{
if(temp[idx]!='\n'&&temp[idx]!=' '&&temp[idx]!='\t')break;
temp.pop_back();
}
get1.emplace_back(temp);
}
while(!get1.empty()&&check(get1.back()))
{
get1.pop_back();
}
while(getline(file2,temp))
{
for(size_t idx=temp.size()-1;idx>=0;idx--)
{
if(temp[idx]!='\n'&&temp[idx]!=' '&&temp[idx]!='\t')break;
temp.pop_back();
}
get2.emplace_back(temp);
}
while(!get2.empty()&&check(get2.back()))
{
get2.pop_back();
}
if(get1.size()!=get2.size())
{
cerr << "文件行数不匹配。"<<"文件 "<<filename1<<" 行数为 "<<get1.size()<< "\n文件 "<<filename2 <<"行数为 "<<get2.size()<<'\n';
return -1;
}
for(int i=0;i<get1.size();i++)
{
if(get1[i]!=get2[i])
{
cerr<<"第 "<< i+1 <<" 行不匹配"<<'\n';
cerr<<"分别为:"<<'\n';
cerr<<get1[i]<<'\n'<<get2[i]<<'\n';
return -1;
}
}
}
}
cerr<<"找不到任何差异\n";
return 0;
}
本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-03-02 15:23:38
搜索 THUPC2023。
/jk/jk/jk/jk/jk/jk/jk/jk/jk/jk/jk/jk/jk/jk/jk
还是第一个

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-03-02 22:21:51
