【游記】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(n^2)\) ,按照提示,果不其然,我只能得到40分,但是至少能進NOIP,穩了
於是我就開始寫其他的題目
現在,我在網上得到了靈感
其實,我們可以貪心做這道題目,對於國內外分開討論
枚舉從 \(1\) 到 \(n\) 的廊橋數量,考慮每次加入一個廊橋能多多少個飛機可以用到廊橋
於是我們就直接將所有的飛機的左右端點放進一個 set
,然后我們每次取一個飛機,這個飛機的左端點是距離上一個飛機右端點最近的一個飛機,所以我們可以直接使用自帶的 lower_bound
來進行一個操作;每成功一個飛機,那么就加一,所以我們如果飛機取到 s.end()
了,我們就直接退出,注意,這里的 s.end()
是 最后一個飛機的位置的下一個位置,所以我們在這里退出
這樣,我們就統計完了一個區的數據
按照上面的做法,我們還可以統計另一個區的數據
然后我們就直接取 \(ans=max_{i=1}^n\{ans1[i]+ans[n-i]\}\) 就得到答案了
#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
,這樣就能避免重復了
然后我們就可以有狀態轉移方程,然后就有解了
#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()
{
//freopen("bracket.in","r",stdin);
//freopen("bracket.out","w",stdout);
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\) 來維護右邊的
分類討論,因為我們要求字典序最小,所以我們按照操作的優先級來進行判斷
- \(A\) 左端等於右端
說明我們可以直接取,而且,右端的數字一定可以從左邊取到
- \(A\) 左端等於 \(B\) 左端
這樣的話,我們左邊還是可以從左邊取,但是右邊的要從右邊取
- \(A\) 右端等於 \(B\) 右端
我們左邊的取后面的,所以要從右邊取,右邊的要取左邊的,所以從左邊取
- \(B\) 左端等於右端
和第一種情況相同,所以可以直接取右端的數字,且最左端的數字也一定可以從右端取到
這樣,我們用手來模擬一下,答案基本就跟答案一樣了
對於開始從右端取,我們反着來一遍就好了,前提是在左邊不能取
記得要數據清空,要不然兩行淚
PS:在初始化雙端隊列的時候要先扔進去幾個,要不然只有一個的話永遠左邊等於右邊
#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啦