CSP-S2023 全场题解
lock
这题就是个模拟吧,赛时被迷惑了以为是什么不可做题,仔细看只有
我们分别从状态串出发,枚举它能达到的答案,加到 set
取个并集,不过注意给定的状态不能是密码,要减掉。注意不要直接计数器减减,不然如果有相同的算在状态里面的会多减,我考场代码就这么被
set<int>S,s; int n,a[10][6]; inline int chang(int x){ return a[x][1]+a[x][2]*10+a[x][3]*100+a[x][4]*1000+a[x][5]*10000; } int main(){ freopen("lock.in","r",stdin); freopen("lock.out","w",stdout); n=read(); for(int i=1;i<=n;i++) for(int j=1;j<=5;j++) a[i][j]=read(); for(int i=1;i<=n;i++){ for(int j=1;j<=5;j++){ for(int o=1;o<=10;o++){ a[i][j]=(a[i][j]+1)%10; if(i==1)s.insert(chang(i)); else if(S.find(chang(i))!=S.end())s.insert(chang(i)); } } for(int j=1;j<=4;j++){ for(int o=1;o<=10;o++){ a[i][j]=(a[i][j]+1)%10; a[i][j+1]=(a[i][j+1]+1)%10; if(i==1)s.insert(chang(i)); else if(S.find(chang(i))!=S.end())s.insert(chang(i)); } } S=s,s.clear(); } for(int i=1;i<=n;i++)if(S.find(chang(i))!=S.end())S.erase(chang(i)); cout<<S.size(); //WA: //int ans=S.size(); //for(int i=1;i<=n;i++)if(S.find(chang(i))!=S.end())ans--; //cout<<ans; return 0; }
game
赛时没有想到栈的 不然说不定就过了(禁止马后炮,就是你菜),就直接打了个
const int N=2e6+7; int n,a[N]; namespace subtask1{ bool f[802][802]; bool work(){ ll ans=0; for(int len=2;len<=n;len+=2){ for(int i=1;i+len-1<=n;i++){ int j=i+len-1; if(a[i]==a[j]&&(len==2||f[i+1][j-1]))f[i][j]=1; else{ for(int k=i+1;k<j;k+=2){ if(f[i][k]&&f[k+1][j]){ f[i][j]=1; break; } } } ans+=f[i][j]; } } cout<<ans; return 0; } } int main(){ string ss; cin>>n>>ss; for(int i=0;i<n;i++)a[i+1]=ss[i]-'a'; if(n<=800)return subtask1::work(),0; return 0; }
赛后听说了
关注到我们消的顺序不会对结果造成影响,因此我们随意地能消则消如果消不掉那么就必然不是可消除串。所以我们维护一个没有被消的字符串栈,每加入一个字符就判断是否与栈顶字符相同,相同就删,如果不相同就加入,这样如果中途发现栈空就说明可以消除,计入答案即可。
如果我们直接这样子枚举左端点和右端点消,复杂度是
瓶颈在于每次枚举左端点的话有很多内容是与之前的一样的,有很多重复运算,所以不能枚举左端点。然后发现一个如果一个串的后缀是可消的,我们这个要一直枚举左端点直到栈为空,而去掉这个后缀后栈也是一样的。
那么我们是不是只需要找前面与当前串的消除栈相同的串的个数就行了?
可以用哈希或者字典树维护消除串。因为每次只增加或者减少一个字符,所以用字典树不能从头加到尾,而是用一个类似于回跳指针维护;对于哈希就是历史哈希值。时间复杂度 unordered_map
可以实现
//hash typedef unsigned long long ull; const ull P=1331,N=2e6+6; int n,r; ull a[N],ans; stack<char>S; map<ull,int>M; int main(){ string ss; cin>>n>>ss; for(int i=0;i<n;i++,M[a[r]]++) if(S.empty()||S.top()!=ss[i]){ S.push(ss[i]); a[++r]=a[r]*P+ss[i]-'a'+1;//must +1!!不然a串与空串无异,卡了挺久 ans+=M[a[r]]; }else{ S.pop(),r--; ans+=M[a[r]]+S.empty(); } cout<<ans; return 0; }
//Trie int n,a[N],tr[26][N],en[N],tot,zero,r; long long ans; stack<int>S; int main(){ string ss; cin>>n>>ss; for(int i=0;i<n;i++){ int w=ss[i]-'a'; if(S.empty()||S.top()!=w){ S.push((w)); if(!tr[w][a[r]])tr[w][a[r]]=++tot; a[++r]=tr[w][a[r]];//注意优先级是从右到左 //等价于a[r+1]=tr[w][a[r]],r++; ans+=++en[a[r]]-1; } else{ S.pop(),r--; if(S.empty())ans+=++zero; else ans+=++en[a[r]]-1; } } cout<<ans; return 0; }
本题的类似题目:CF1223F Stack Exterminable Arrays。
不过这个因为值域太大不能用字典树,单哈希也能过。
struct
一眼大模拟,考场上没想到正解,没多少时间了就寻思着顺手拿个
对于
//错因: //一开始什么东西都没有的时候ERR没有预处理导致很多点RE,结构体加完以后没有把尾部内存加回去,导致地址错误 //再一次体验了什么叫少写两行代码流两行泪。(悲) int n,tot;//number of struct struct node{ string typ; ll st; }; struct Struct{ string me,typ[102],nam[102]; int num,len=0,siz=0; }; map<int,string>S;//address for opt=4 map<string,node>M;//for opt=3 map<string,Struct>Str; inline int gett(string tpy){ if(tpy=="byte")return 1; if(tpy=="short")return 2; if(tpy=="int")return 4; if(tpy=="long")return 8; return Str[tpy].siz; } ll t; int main(){ n=read(); S[0]="ERR";//少写这句 for(int ww=1;ww<=n;ww++){ int opt=read(); if(opt==1){ Struct p; cin>>p.me; p.num=read(); ll now=0; for(int i=1;i<=p.num;i++){ cin>>p.typ[i]>>p.nam[i]; p.len=max(p.len,gett(p.typ[i])); } for(int i=1;i<=p.num;i++){ if(now%gett(p.typ[i])!=0)now=(now/gett(p.typ[i])+1)*gett(p.typ[i]); now+=gett(p.typ[i]); } if(now%p.len!=0)now=(now/p.len+1)*p.len; p.siz=now; Str[p.me]=p; printf("%d %d\n",p.siz,p.len); }else if(opt==2){ string tpy,nam; cin>>tpy>>nam; if(Str.find(tpy)!=Str.end()){ Struct p=Str[tpy]; if(t%p.len!=0)S[t]="ERR",t=(t/p.len+1)*p.len; printf("%lld\n",t); M[nam]={p.me,t}; for(int i=1;i<=p.num;i++){ if(t%gett(p.typ[i])!=0)S[t]="ERR",t=(t/gett(p.typ[i])+1)*gett(p.typ[i]); S[t]=nam+"."+p.nam[i]; M[nam+"."+p.nam[i]]={p.typ[i],t}; t+=gett(p.typ[i]); } if(t%p.len!=0)S[t]="ERR",t=(t/p.len+1)*p.len;//少写这句 S[t]="ERR"; continue; } if(t%gett(tpy)!=0)S[t]="ERR",t=(t/gett(tpy)+1)*gett(tpy); printf("%lld\n",t); S[t]=nam; M[nam]={tpy,t}; t+=gett(tpy); S[t]="ERR"; }else if(opt==3){ string w; cin>>w; printf("%lld\n",M[w].st); }else{ ll addr=read(); auto it=--S.upper_bound(addr); cout<<(it->second)<<endl; } } return 0; }
思考正解吧,正解肯定不能是完全展开所有变量,因为这个
本题的复杂度瓶颈在结构体嵌套,我们发现除了在操作 map
,然后每次遇到结构体就递归向内部查找即可,复杂度
不开 long long 见祖宗。
具体细节见代码:
struct variate{ string typ,name; ll addr; }ERR; struct Struct{ int n,len=0;//元素个数,对齐规则 ll size=0;//占用大小 map<ll,variate>atn;//address to name 由相对位置到变量名 map<string,variate>nta;//name to address(and type) }; map<string,Struct>str;//类型对应的结构体 map<string,variate>st;//无嵌套的变量查找 map<ll,variate>ad;//无嵌套的地址查找 inline int find(string typ){ if(typ=="byte")return 1; if(typ=="short")return 2; if(typ=="int")return 4; if(typ=="long")return 8; return 0; } void define_struct(ll t=0){ Struct p;//一定要赋初值 string name; cin>>name; p.n=read(); for(int i=1,len;i<=p.n;i++){ string typ,name; cin>>typ>>name; len=find(typ);ll siz=len; if(!len)len=str[typ].len,siz=str[typ].size; p.len=max(p.len,len); if(t%len)p.atn[t]=ERR,t=(t/len+1)*len; p.atn[t]={typ,name,t}; p.nta[name]={typ,name,t}; t+=siz; } if(t%p.len)p.atn[t]=ERR,t=(t/p.len+1)*p.len; p.size=t; p.atn[t]=ERR; str[name]=p; printf("%lld %d\n",p.size,p.len); } int n;ll t; inline void define_element(){ string typ,name; cin>>typ>>name; int len;ll siz;//这里siz要ll if(str.find(typ)!=str.end())len=str[typ].len,siz=str[typ].size; else len=siz=find(typ); if(t%len)ad[t]=ERR,t=(t/len+1)*len; printf("%lld\n",t); st[name]={typ,name,t}; ad[t]={typ,name,t}; t+=siz; ad[t]=ERR; } void search_element(){ string name,top; int lentop; cin>>name; for(lentop=0;lentop<name.length()&&name[lentop]!='.';lentop++); top=name.substr(0,lentop); if(lentop==name.length())return printf("%lld\n",st[top].addr),void(); Struct from=str[st[top].typ]; ll addr=st[top].addr; while(name.length()>lentop){ name=name.substr(lentop+1); for(lentop=0;lentop<name.length()&&name[lentop]!='.';lentop++); top=name.substr(0,lentop); addr+=from.nta[top].addr; if(name.length()>lentop)from=str[from.nta[top].typ]; } printf("%lld\n",addr); } void search_address(){ ll addr=read(); variate p=(--ad.upper_bound(addr))->second; string name=p.name; addr-=p.addr; while(!find(p.typ)){ name+="."; Struct o=str[p.typ]; p=(--o.atn.upper_bound(addr))->second; name+=p.name; addr-=p.addr; if(p.name=="ERR")name="ERR"; } cout<<name<<endl; } int main(){ freopen("struct.in","r",stdin); freopen("struct.out","w",stdout); n=read(); ad[0]=ERR={"long","ERR",0}; while(n--){ int opt=read(); if(opt==1)define_struct(); else if(opt==2)define_element(); else if(opt==3)search_element(); else search_address(); } return 0; }
打大模拟的时候一定要注意力集中,不能凭借肌肉记忆,不然打代码一时,调代码一世。认真打的话就可以微调过了。(虽然写其他的题目每一步也需要非常清楚自己在干什么,不能神游)。
tree
考场上第二接近满分的题,虽然也能随便被 hack,但是在 ccf 上也有 90 分的好成绩。
在我被软件问题调试和第一题浪费了
看见他的第一眼:树题唉,大概率树形 dp,之前模拟赛做飞起来了,可能可做?
第二眼:最小答案?保证在
第三眼:关系线性?分类讨论加等差数列公式,懒得管推什么公式了,
复杂度
然后打代码就完事了。因为 ccf 样例太水导致我甚至等差数列的公式打错了都能全部通过。所以也预料到了赛后被 hack。
typedef __int128 INT; int n; int cal1(int las,ll b,ll c,INT a){ INT h2=b+las*c; if(h2>=a)return n; int l=0,r=n; while(l<r){ int mid=(l+r+1>>1); if((INT)(h2+b+c*mid)*(las-mid+1)>=a*2)l=mid;//A.P else r=mid-1; } return l; } int cal2(int las,ll b,ll c,INT a){ ll o=(b-1)/(-c);//f(o)>=1 int l=0,r=n; while(l<r){ int mid=(l+r+1>>1); INT S; if(mid>o)S=las-mid+1; else if(las>o)S=(INT)((INT)b*2+c*(o+mid))*(o-mid+1)/2+(las-o); else S=(INT)((INT)b*2+c*(las+mid))*(las-mid+1)/2; if(S>=a)l=mid; else r=mid-1; } return l; } ll a[N],b[N],c[N],t[N],f[N],id[N]; vector<int>F[N]; bool dfs(int x,int fa){ f[x]=t[x]; for(int i=0;i<F[x].size();i++){ int y=F[x][i]; if(y==fa)continue; if(!dfs(y,x))return 0; f[x]=min(f[x],f[y]-1); if(f[x]<1)return 0; } return 1; } inline bool cmp(int x,int y){ return f[x]<f[y];//排序小技巧 } bool check(int x){ for(int i=1;i<=n;i++){ id[i]=i; if(c[i]>=0){ int w=cal1(x,b[i],c[i],a[i]); if(w==0)return 0; t[i]=w; }else{ int w=cal2(x,b[i],c[i],a[i]); if(w==0)return 0; t[i]=w; } } if(!dfs(1,0))return 0; sort(id+1,id+1+n,cmp); for(int i=1;i<=n;i++) if(i>f[id[i]])return 0; return 1; } int main(){ freopen("tree.in","r",stdin); freopen("tree.out","w",stdout); n=read(); for(int i=1;i<=n;i++){ a[i]=read(),b[i]=read(),c[i]=read(); } for(int i=1;i<n;i++){ int u=read(),v=read(); F[u].push_back(v); F[v].push_back(u); } int l=n,r=1e9+2; while(l<=r){ int mid=l+r>>1; if(check(mid))r=mid-1; else l=mid+1; } cout<<l; return 0; }
后记
不得不说这次
finished at 2023.11.3 15:36
本文作者:NBest
本文链接:https://www.cnblogs.com/NBest/p/17807722.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步