CSP2023 游记
发现自己越来越智障了。。。
2:40 开考。四个题看了一遍,T1看起来挺水,T2像某种区间dp,T3大模拟,T4很神秘,完全不会。
2:50 顺序开题。看到T1每次只转一个或连着的两个,看起来很有性质的样子,于是开始研究,发现可以根据五列是否相同来判,然后开始分讨。
(实际上是错的,但没完全错。整场看到5列,每列0~9,居然从没想过1e5的暴力枚举。考完发现把分讨变成判定就是正解。)
(upd10.29:实际上还看错题了。。。以为要求正确密码转一次得到所有状态,所以上面写的很奇怪)
30min 后调过小样例,发现大样例挂了。于是开始研究。
3:50 发现自己没看懂大样例。发现时间过去1h,赶紧开T2。
看到数据范围2e6,发现可能是线性dp。觉得自己想不出来,而且要是再挂可能就保龄了,开始想 \(n^2\) 做法。
4:10 大概想出来了。开码。
4:40 没过大样例&没调出来。怎么会是呢。15min写了拍和 \(n^3\) 暴力,发现在下面情况假了:
我的做法相当于把前两个a配对,但4567也是答案。彻底假了。好似。
(upd10.29:没完全假。枚举左端点就是 \(O(n)\)做法)
发现5:00了。急急急。T2换成 \(n^3\) 暴力,开始犹豫回去看T1还是写T3。觉得T1大家都切了(事实上确实都切了,只有我没想到暴力),于是润回去看T1继续分讨。
5:30 发现不对。按照我的做法大样例和答案匹配不上,问志愿者也知道题没错,开始怀疑分讨正确性。(还是没想到暴力枚举)随便乱搞,寄希望于CCF的数据强度了。
这时还没说延不延时,发现可能只剩下1h,T3T4都没写,T1T2加起来也就65(和最后的分一样)。赶紧看T3。
。。。这T3大模拟是给人类写的?开码A性质15pts部分分。
大概15min码完,懒得写拍(实际上没有暴力正解也没法写拍),自己造了几组小样例,感觉没啥问题了(问题很大,15pts挂了)。
开T4。发现每棵树每天长的高度是一次函数的形式,可能用类似李超线段树的东西维护,对于c=0每棵树每天长的高度确定,天数也知道,但神秘贪心很可能假,但没办法链和菊花图不会求天数(感觉像是二分,但还是不会),写了神秘贪心。(然而并没有骗到分)
然后就剩不到20min,查freopen和文件夹,然后就over了
出考场问各位巨佬神秘T1,都说模拟就行,贺老师轻松给出大样例解释,发现自己小丑了。(在车上突然反应过来巨佬们说的模拟是暴力枚举不是分讨,更小丑了)
预计得分:30+35+15+0=80
洛谷/小图灵/xyd得分:30+35+0+0=65
实际得分:30+35+0+0=65(不得不说测的真准,我也挂的很稳定)
总结:
好似。打的比任何一场模拟都炸。
反思一下考场上的问题:
-
小数据范围不想暴力枚举。。。
-
题没读懂。以为要求只转一次得到所有状态。(
被自己蠢哭了) -
时间分配不均。感觉一半时间都浪费在T1的分讨上导致T3T4没时间写了
-
考试的时候可能有点着急,感觉思维不够灵活,开局想偏一整场没想过别的方法,死在T1上了
-
看不懂大样例不要用眼盯,写个暴力试一下(
然后就会发现这是正解) -
写之前先想好,确定是对的在写,T2的假做法卡了我半个多小时。。。
-
代码能力太差。T3大模拟全挂了。。。
这场确实很难形容。。。希望自己能记住这场的教训,以后别犯这种弱智错误了/fn
赛后补题:
\(10^5\) 枚举所有可能答案 再枚举n个状态 考虑该答案是否正确 分讨
相同位数数量 \(sam=5\) 或\(sam\le 2\)时 一定不满足(要求 \(n\) 个状态都不是答案 \(sam\le 2\)怎么转都无法实现)
\(sam=4\) 一定满足(不同的一位直接转)
剩下 \(sam=3\) 的情况:
考虑不同的两个位置 \(pos1\ pos2\) 若两个位置不相邻 一定不满足(要求转两个需要相邻)
否则考虑能否转相同幅度达到当前状态 注意可能有进位情况 +-10即可
时间复杂度 \(O(10^5n)\)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
inl int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
inl void write(int x){
if(x<0){x=-x;putchar('-');}
if(x>9)write(x/10);
putchar(x%10+'0');
}
inl void writei(int x){write(x);putchar(' ');}
inl void writel(int x){write(x);putchar('\n');}
int n,a[11][11],t[11],pos1,pos2,ans;
signed main(){
n=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=5;j++)
a[i][j]=read();
for(t[1]=0;t[1]<=9;t[1]++){
for(t[2]=0;t[2]<=9;t[2]++){
for(t[3]=0;t[3]<=9;t[3]++){
for(t[4]=0;t[4]<=9;t[4]++){
for(t[5]=0;t[5]<=9;t[5]++){
int flag=1;
for(int i=1;i<=n;i++){
int sam=0;
for(int pos=1;pos<=5;pos++){
sam+=(t[pos]==a[i][pos]);
if(t[pos]^a[i][pos]){
pos2=pos1;
pos1=pos;
}
}
if(sam==5||sam<=2){flag=0;break;}
if(sam==4)continue;
if(abs(pos1-pos2)^1){flag=0;break;}
if(a[i][pos1]-t[pos1]==a[i][pos2]-t[pos2])continue;
if(a[i][pos1]-t[pos1]+10==a[i][pos2]-t[pos2])continue;
if(a[i][pos1]-t[pos1]-10==a[i][pos2]-t[pos2])continue;
flag=0;break;
}
ans+=flag;
}
}
}
}
}
writel(ans);
return 0;
}
赛时代码: \(O(n^3)\) 区间dp。
设 \(f[l][r]\) 表示区间 \([l,r]\) 能否合并。
两种情况:中间消完两边消,或者分两段消。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
const int N=8005;
const int M=1e5+5;
inl int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
inl void write(int x){
if(x<0){x=-x;putchar('-');}
if(x>9)write(x/10);
putchar(x%10+'0');
}
inl void writel(int x){write(x);putchar('\n');}
int n,f[N][N],ans;
char c[N];
stack<int>st;
signed main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
n=read();
scanf("%s",c+1);
for(int i=1;i<=n;i++)f[i][i-1]=1;
for(int len=2;len<=n;len+=2){
for(int l=1;l+len-1<=n;l++){
int r=l+len-1;
if(c[l]==c[r]&&f[l+1][r-1]){f[l][r]=1,ans++;continue;}
for(int k=l+1;k<r;k+=2){
if(f[l][k]&&f[k+1][r]){f[l][r]=1,ans++;break;}
}
}
}
writel(ans);
return 0;
}
\(O(n^2)\) 做法:
枚举答案左端点,每次看当前元素是否等于栈顶元素,是则弹出,累加答案,否则push进去(类似括号序列)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
const int N=8005;
const int M=1e5+5;
inl int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
inl void write(int x){
if(x<0){x=-x;putchar('-');}
if(x>9)write(x/10);
putchar(x%10+'0');
}
inl void writel(int x){write(x);putchar('\n');}
int n,ans;
char c[N];
stack<int>st;
signed main(){
// freopen("game.in","r",stdin);
// freopen("game.out","w",stdout);
n=read();
scanf("%s",c+1);
for(int i=1;i<=n;i++){
stack<char>st;
for(int j=i;j<=n;j++){
if(!st.empty()&&c[j]==st.top())
st.pop(),ans+=st.empty();
else st.push(c[j]);
}
}
writel(ans);
return 0;
}
\(90pts\) 做法:
我们发现 当栈的状态和之前某位置时相等 那么说明这区间能全消
可以 hash+map 实现 理论复杂度 \(O(n\log n)\)
但不知道为啥 有两个点T了 有巨佬知道可以帮忙看一下/bx
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
#define int __int128
const int N=2e6+5;
const int M=1e5+5;
const int base=131;
inl int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
inl void write(int x){
if(x<0){x=-x;putchar('-');}
if(x>9)write(x/10);
putchar(x%10+'0');
}
inl void writel(int x){write(x);putchar('\n');}
int n,ans;
char c[N];
stack<char>st;
map<int,int>mp;
inl int hash1(stack<char>st){
int ans=0;
while(!st.empty()){
int x=st.top()-'a'+1;st.pop();
ans=ans*base+x;
}
return ans;
}
signed main(){
freopen("1.in","r",stdin);
// freopen("game.out","w",stdout);
n=read();
scanf("%s",c+1);
mp[0]=1;
for(int i=1;i<=n;i++){
if(!st.empty()&&c[i]==st.top())
st.pop();
else st.push(c[i]);
ans+=mp[hash1(st)];
mp[hash1(st)]++;
}
writel(ans);
return 0;
}
正解:设 \(f_i\) 表示以 \(i\) 为结尾的合法序列数量 \(ans=\sum f_i\)
考虑转移 发现只要求出以 \(i\) 结尾最短的合法序列
设 \(lst_i\) 为以 \(i\) 结尾最短的合法序列的开头位置 起始 \(j\) 等于 \(i-1\) 然后往前暴力跳 \(j=lst_j-1\) 直到 \(c_i=c_j\)
( \(j\) 到 \(lst_j\) 是合法序列 那么 \(f_lst-1\) 为下一段开头)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
#define int ll
const int N=2e6+5;
const int M=1e5+5;
const int base=131;
inl int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
inl void write(int x){
if(x<0){x=-x;putchar('-');}
if(x>9)write(x/10);
putchar(x%10+'0');
}
inl void writel(int x){write(x);putchar('\n');}
int n,ans,lst[N],f[N];
char c[N];
signed main(){
n=read();
scanf("%s",c+1);
for(int i=1;i<=n;i++){
int j=i-1;
if(c[j]==c[i]){f[i]=f[j-1]+1;lst[i]=j;continue;}
while(lst[j]){
j=lst[j]-1;
if(c[j]==c[i]){f[i]=f[j-1]+1;lst[i]=j;break;}
}
}
for(int i=1;i<=n;i++)ans+=f[i];
writel(ans);
return 0;
}
赛时 \(15pts\) 做法:\(map\) 存元素对应的地址 每个地址开个 \(string\) 存元素
挂分原因:二操作没输出 \(k\) /fn
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
const int M=2e5+5;
#define ll long long
#define inl inline
inl int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
inl void write(int x){
if(x<0){x=-x;putchar('-');}
if(x>9)write(x/10);
putchar(x%10+'0');
}
inl void writel(int x){write(x);putchar('\n');}
int n,k,op,p;
string vis[805];
map<string,int>mp;
string a,b;
signed main(){
// freopen("struct.in","r",stdin);
// freopen("struct.out","w",stdout);
n=read();
for(int i=1;i<=n;i++){
op=read();
if(op==2){
cin>>a>>b;
if(a=="byte"){
writel(k);// 这里/fn
mp[b]=k;
vis[k++]=b;
}
if(a=="short"){
while(k%2)k++;
writel(k);
mp[b]=k;
for(int i=1;i<=2;i++)vis[k++]=b;
}
if(a=="int"){
while(k%4)k++;
writel(k);
mp[b]=k;
for(int i=1;i<=4;i++)vis[k++]=b;
}
if(a=="long"){
while(k%8)k++;
writel(k);
mp[b]=k;
for(int i=1;i<=8;i++)vis[k++]=b;
}
}
if(op==3){
cin>>a;
writel(mp[a]);
}
if(op==4){
p=read();
if(vis[p].empty())puts("ERR");
else cout<<vis[p]<<endl;
}
}
return 0;
}