【游记】CSP-S2021 退役记
Day -1
今天早上一起床,外面便是阴森森的(PS:似乎暗示了今天的考试题目),朝阳似乎没有漏出脸来,但是还是给积雨云染上了一层淡淡的红妆。雨慢慢地下着,布满水洼的地上在脚的作用下溅起了一朵朵水花,我走向了教室明德楼
拿起了笔袋,拿起了《信息学奥赛一本通<初赛篇>》,我向大成楼赶去,等我再打着伞走到楼下的时候,天似乎变得不是那么羞涩,平静起来了,成熟起来了。
走过林荫小道,同学们来来往往,陆陆续续地赶向明德楼教室,准备开始早自习,准备一天的周考,而我恰恰相反,与他们相向而行,走向了机房
机房还是那个味道,但是比原来淡了许多,灰尘似乎在雨水之下收起了他的猖狂,安静地待在地上,无论机房内,还是机房外
打开灯,打开电脑,一阵阵熟悉感扑面而来,恩,这才是机房啊,我的生活方式
先打开了洛谷有题,做一做历年的真题,然后发现了一个规律,2013年和2020年初赛的题目有一些重复,于是我想,那是不是我应该看一下2014年的题目,我想着,确实,我也这么做了。
慢慢地,时间到了7:20,我去食堂吃了一个早饭,一碗相对于这鬼天气热腾腾的西红柿鸡蛋面,内心平和了下来,又回到了机房
回到了机房,f老师不过一会也来了,于是我就被征用成为苦工,打扫教室,累死
打扫完卫生之后我又坐到了我可怜的位置上面,继续我的初赛复习
打开了Tim,看到很多人都在祝福关于CSP RP++的事情
我也参与其中
和之前认识的OI Friend聊了聊之前的题目以及今天的预判之后,也快到了考试的时间了
考试的同学们陆陆续续地进来(没错,我考试在我训练的机房考,并且我坐在我自己的位置上考)
我看到了大佬MC_Laucher ,还有一些十堰和随州的同僚们,他们也都自信满满
9:30发卷子,拿到卷子之后,我发现,卷子又比去年多了3页呢,看来今天的题量很大呢
看到第一题,我不会,因为我没有用过Linux,压根就不知道qaq,然后光荣地错了
然后一直做到阅读程序
今年的阅读程序非常恶心,有的是线段树,有的是base64,一个个在我的眼前晃来晃去,一串串的代码和输入输出在纸上跳动着,我似乎陷入了困境,惊呼,这是什么鬼题!!
硬着头皮继续向下做,做到完善程序的时候我实在hold不住了,这是什么鬼题目,为什么还可以这样,什么分块,笛卡尔树,四毛子算法在我的眼前眼花缭乱,此时,时间只剩下了不足挂齿的40分钟
然后就没有然后了,一波乱搞,我就觉得我要退役了
希望能进复赛吧
upd:初赛70.5,进复赛了QAQ
Day -0.7
快了快了,好紧张
Day1
现在是
2021.10.23日 9:31
CSP-S即将在今天下午举行
而我,即将在这个时候分出自己的胜负,于此
如果没有到200分的话,或许我就退役了,但是历史的确是向前发展的
因为,人生,确是会有取舍
然而,站在人生的分叉路,一分,两分,也要分出胜负,拼尽自己的所有,无问西东
CSP退役了
我只拿到了40-50分
四年OI一场空
我本为天真无邪的男孩,确为OI付出了大半青春,确实是一大幸事
为了OI,我还有NOIP,但是我必须要准备期中考试,因为只有通过期中考试的良好表现,我才能说服父母,来到NOIP的殿堂,然而又通过NOIP的良好表现,我才能通过NOIP进入省队,完成我的梦想
至此,我愿以梦为马,不负韶华,OI与文化课齐飞,代码共作业一色
下面题解
T1 廊桥分配
思路
打开第一题,我看到了廊桥分配,阅读完题目和样例,我觉得胸有成竹,觉得,这道题目稳了,于是,我就开始打我的代码
我在考场上做的时候,想到了一个前缀和的代码,把所有的时间节点放在一个数轴上,我们可以先按照时间进行排序
将飞机入场的时间节点赋值为1,将飞机出厂的时间结点赋值为-1,然后我们离散化一下,每一个地方在时间维度上进行一个求最大值
但是我发现,因为有一些飞机根本没有办法使用廊桥,所以,就直接被过掉了,于是我上面的算法假了,花掉了我30min的时间
然后我看着时间不多了,于是我就开了一个暴力,写稳了40分,时间复杂度 O(n2) ,按照提示,果不其然,我只能得到40分,但是至少能进NOIP,稳了
于是我就开始写其他的题目
现在,我在网上得到了灵感
其实,我们可以贪心做这道题目,对于国内外分开讨论
枚举从 1 到 n 的廊桥数量,考虑每次加入一个廊桥能多多少个飞机可以用到廊桥
于是我们就直接将所有的飞机的左右端点放进一个 set ,然后我们每次取一个飞机,这个飞机的左端点是距离上一个飞机右端点最近的一个飞机,所以我们可以直接使用自带的 lower_bound 来进行一个操作;每成功一个飞机,那么就加一,所以我们如果飞机取到 s.end() 了,我们就直接退出,注意,这里的 s.end() 是 最后一个飞机的位置的下一个位置,所以我们在这里退出
这样,我们就统计完了一个区的数据
按照上面的做法,我们还可以统计另一个区的数据
然后我们就直接取 ans=maxi=1n{ans1[i]+ans[n−i]} 就得到答案了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| #include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <string> #include <set> #define int long long using namespace std; const int maxn=100005; struct node{ int l,r; bool operator<(const node &x) const { return l<x.l; } }a1[maxn],a2[maxn]; int n,m1,m2; int ans1[maxn],ans2[maxn]; set <node> s; signed main() { cin>>n>>m1>>m2; for(int i=1;i<=m1;i++) cin>>a1[i].l>>a1[i].r; for(int i=1;i<=m2;i++) cin>>a2[i].l>>a2[i].r; s.clear(); for(int i=1;i<=m1;i++) s.insert(a1[i]); for(int i=1;i<=n;i++) { int pos=0; int c=0; while(1) { auto it=s.lower_bound(node{pos,0}); if(it==s.end()) break; pos=it->r; s.erase(it); c++; } ans1[i]=ans1[i-1]+c; } s.clear(); for(int i=1;i<=m2;i++) s.insert(a2[i]); for(int i=1;i<=n;i++) { int pos=0; int c=0; while(1) { auto it=s.lower_bound(node{pos,0}); if(it==s.end()) break; pos=it->r; s.erase(it); c++; } ans2[i]=ans2[i-1]+c; } int ans=-1e9; for(int i=0;i<=n;i++) ans=max(ans1[i]+ans2[n-i],ans); cout<<ans<<'\n'; return 0; }
|
T2 括号序列
思路
第二题,我拿到之后竟然完全没有作为 OIer 的自觉性,我竟然没有想到区间DP,我简直太水了草
我想写一个暴力,但是暴力似乎不太行,因为我对于某些括号序列的判断出了问题,所以我有问题
然后我就写了一个某些括号序列情况都没有程序,不知道能得多少分qaq,似乎hydro的数据非常强,但是我能搞个5分左右吧
不知道,也不想知道
这道题目,我们首先看题目,要搞懂括号序列有哪几类
具体而言,小 w 定义“超级括号序列”是由字符 (、)、* 组成的字符串,并且对于某个给定的常数 k,给出了“符合规范的超级括号序列”的定义如下:
()、(S) 均是符合规范的超级括号序列,其中 S 表示任意一个仅由不超过 k 个字符 * 组成的非空字符串(以下两条规则中的 S 均为此含义);
- 如果字符串
A 和 B 均为符合规范的超级括号序列,那么字符串 AB、ASB 均为符合规范的超级括号序列,其中 AB 表示把字符串 A 和字符串 B 拼接在一起形成的字符串;
- 如果字符串
A 为符合规范的超级括号序列,那么字符串 (A)、(SA)、(AS) 均为符合规范的超级括号序列。
- 所有符合规范的超级括号序列均可通过上述 3 条规则得到。
例如,若 k=3,则字符串 ((**()*(*))*)(***) 是符合规范的超级括号序列,但字符串 *()、(*()*)、((**))*)、(****(*)) 均不是。特别地,空字符串也不被视为符合规范的超级括号序列。
看到这里 ,我们知道了括号序列可以有
()
(s)
(A),(AS),(SA)
ASA
这四类,我们将 1 ~ 3 称为包围类,4 称为并排类
我们可以设
- f[i][j][0] 表示 [i,j] 中,完全合法的括号方案数量
- f[i][j][1] 表示 [i,j] 中,
AS 的括号方案数量
- f[i][j][2] 表示 [i,j] 中,
SA 的括号方案数量
- f[i][j][3] 表示 [i,j] 中,
S 的括号方案数量
其中并排类会重复计数,那我们就加一个强限制条件,我们可以枚举 ASA 中的第一个 A 的位置,然后对于这个 A ,一定是一个包围类的 A ,这样就能避免重复了
然后我们就可以有状态转移方程,然后就有解了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| #include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <string> #define int long long using namespace std; const int mod=1e9+7; const int maxn=505; int f[maxn][maxn][4]; int n,m,k; string s; signed main() { cin>>n>>m; cin>>s; if(m>=1) { for(int i=0;i<n;i++) { if(s[i]=='?'||s[i]=='*') f[i][i][3]=1; } } for(int len=2;len<=n;len++) { for(int i=0;i<n&&i+len-1<n;i++) { int j=i+len-1; if((s[i]=='?'&&s[j]==')')||(s[i]=='('&&s[j]=='?')||(s[i]=='?'&&s[j]=='?')||(s[i]=='('&&s[j]==')')) { if(i+1>j-1) f[i][j][0]=1; else f[i][j][0]=(f[i][j][0]+f[i+1][j-1][0]+f[i+1][j-1][1]+f[i+1][j-1][2]+f[i+1][j-1][3])%mod; } for(int k=i+1;k<j-1;k++) { if((s[i]=='('||s[i]=='?')&&(s[k]==')'||s[k]=='?')) { if(i+1==k) { f[i][j][0]=(f[i][j][0]+f[k+1][j][0])%mod; f[i][j][0]=(f[i][j][0]+f[k+1][j][2])%mod; } else { f[i][j][0]=(f[i][j][0]+(f[i+1][k-1][0]+f[i+1][k-1][3]+f[i+1][k-1][1]+f[i+1][k-1][2])*f[k+1][j][0])%mod; f[i][j][0]=(f[i][j][0]+(f[i+1][k-1][0]+f[i+1][k-1][3]+f[i+1][k-1][1]+f[i+1][k-1][2])*f[k+1][j][2])%mod; } } } for(int k=j-1;k>=j-m&&k>i;k--) f[i][j][1]=(f[i][j][1]+f[i][k][0]*f[k+1][j][3])%mod; for(int k=i+1;k<=i+m&&k<j;k++) f[i][j][2]=(f[i][j][2]+f[i][k-1][3]*f[k][j][0])%mod; if(len<=m) { int flag=1; for(int k=i;k<=j;k++) { if(s[k]=='('||s[k] == ')') { flag=0; break; } } if(flag) f[i][j][3]=1; } } } cout<<f[0][n-1][0]<<endl; return 0; }
|
引用:2021 CSP-S2 题解(完整版) - CodeShark的文章 - 知乎 https://zhuanlan.zhihu.com/p/426285214
T3 回文
思路
这道题目是难度第二低的题目
首先,我们观察性质
如果设步骤为第 st 步 左边取第一个的话,我们将会找到下一个跟他一样的数字,这个数字将会在 2n−st+1 步被取到,根据这个思路,我们第一次取的时候可以把序列划分为两个部分,因为在对应的数字的地方,那个地方的左边只能从左边取,右边只能从右边取
我们用两个双端队列来维护序列
第一个双端队列设为 A 来维护左边的
第二个双端队列设为 B 来维护右边的
分类讨论,因为我们要求字典序最小,所以我们按照操作的优先级来进行判断
说明我们可以直接取,而且,右端的数字一定可以从左边取到
这样的话,我们左边还是可以从左边取,但是右边的要从右边取
我们左边的取后面的,所以要从右边取,右边的要取左边的,所以从左边取
和第一种情况相同,所以可以直接取右端的数字,且最左端的数字也一定可以从右端取到
这样,我们用手来模拟一下,答案基本就跟答案一样了
对于开始从右端取,我们反着来一遍就好了,前提是在左边不能取
记得要数据清空,要不然两行泪
PS:在初始化双端队列的时候要先扔进去几个,要不然只有一个的话永远左边等于右边
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
| #include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <string> #include <deque> using namespace std; const int maxn=500005; int T,n; int x[maxn<<1],st; deque <int> a,b; char s[maxn<<1]; bool flag; void work() { cin>>n; for(int i=1;i<=(n<<1);i++) cin>>x[i]; a.clear(),b.clear(); memset(s,0,sizeof(s)); flag=false; st=0; a.push_back(x[1]); a.push_back(x[2]); for(int i=3;i<=(n<<1);i++) { if(a.front()!=a.back()) a.push_back(x[i]); else b.push_back(x[i]); } while(st<n) { if(flag) break; flag=true; if(a.size()>1&&a.front()==a.back()) { flag=false; st++; s[st]='L'; s[2*n-st+1]='L'; a.pop_front(); a.pop_back(); } else if(a.size()&&b.size()&&a.front()==b.front()) { flag=false; st++; s[st]='L'; s[2*n-st+1]='R'; a.pop_front(); b.pop_front(); } else if(a.size()&&b.size()&&a.back()==b.back()) { flag=false; st++; s[st]='R'; s[2*n-st+1]='L'; a.pop_back(); b.pop_back(); } else if(b.size()>1&&b.front()==b.back()) { flag=false; st++; s[st]='R'; s[2*n-st+1]='R'; b.pop_front(); b.pop_back(); } } if(!flag) { for(int i=1;i<=(n<<1);i++) cout<<s[i]; cout<<'\n'; return ; } a.clear(),b.clear(); memset(s,0,sizeof(s)); flag=false; st=0; b.push_front(x[2*n]); b.push_front(x[2*n-1]); for(int i=2*n-2;i>0;i--) { if(b.front()!=b.back()) b.push_front(x[i]); else a.push_front(x[i]); } a.push_back(b.front()); b.pop_front(); while(st<n) { if(flag) { cout<<-1<<'\n'; return ; } flag=true; if(a.size()>1&&a.front()==a.back()) { flag=false; st++; s[st]='L'; s[2*n-st+1]='L'; a.pop_front(); a.pop_back(); } else if(a.size()&&b.size()&&a.front()==b.front()) { flag=false; st++; s[st]='L'; s[2*n-st+1]='R'; a.pop_front(); b.pop_front(); } else if(a.size()&&b.size()&&a.back()==b.back()) { flag=false; st++; s[st]='R'; s[2*n-st+1]='L'; a.pop_back(); b.pop_back(); } else if(b.size()>1&&b.front()==b.back()) { flag=false; st++; s[st]='R'; s[2*n-st+1]='R'; b.pop_front(); b.pop_back(); } } for(int i=1;i<=(n<<1);i++) cout<<s[i]; cout<<'\n'; return ; } int main() {
cin>>T; while(T--) work(); return 0; }
|
T4 交通规划
不会,也会不了了
这样,CSP四道题目就过了一遍了
在考试的最后10min,我还在着急忙慌地加上输入输出的头文件
最终,怀着满心的不甘,我离开了考场,心里却还在沉浸在几道题目没有做出来的悲痛中,而又无法确定自己是否得了分,害怕自己就像去年一样,保龄,然后最后,不光荣地退役
在走出考场之后,我遇见了zlw,一起讨论起来,两个人做出来的题目也差不多吧,是菜鸡互啄了
走出华科的计算机大楼
风不是很大,但是吹在身上像吹在心上一样
凉凉的,但是神情严肃,不为所动
我走出大门,看见了家长,我不知道该怎么说,因为,我对自己也没有底
只能学着其他出来的人说:炸了,保龄了
但是我不甘,我怎么能这么退役?
四年OI一场空?
不应该啊
但最后,还是凭借着微弱的分数进了NOIP
时间在前进着
尽管拉普拉斯妖
但我们的未来依然是未知的
竞赛,也是未知的
这或许就是生活的乐趣吧
永远未知
永远奋斗
永远
有伤心,开心
有为自己理想的付出
无论现实生活怎么打击,干他就完了!
Day 27
明天考NOIP啦