2024年天梯赛题解
PTA | 程序设计类实验辅助教学平台 (pintia.cn)
L1-097 编程解决一切
L1-098 再进去几个人
L1-099 帮助色盲
L1-100 四项全能
L1-101 别再来这么多猫娘了!
L1-102 兰州牛肉面
L1-103 整数的持续性
L1-104 九宫格
L2-049 鱼与熊掌
L2-050 懂蛇语
L2-051 满树的遍历
L2-052 吉利矩阵
L3-037 夺宝大赛
L3-038 工业园区建设
L3-039 攀岩
L1
1 签到
#include <bits/stdc++.h> using namespace std; typedef long long ll; int read() { int x;scanf("%d",&x);return x; } int main() { // freopen("1.in","r",stdin); // freopen("3.out","w",stdout); cout<<"Problem? The Solution: Programming."; }
2 没看懂题,但是猜题意,输出b-a
#include <bits/stdc++.h> using namespace std; typedef long long ll; int read() { int x;scanf("%d",&x);return x; } int main() { // freopen("1.in","r",stdin); // freopen("3.out","w",stdout); int a=read(),b=read(); cout<<b-a; }
3 模拟
第一行:如果a=2或者b=1,输出-,否则如果a=0,输出biii,否则输出dudu
第二行:如果是绿灯,输出move,否则输出stop
#include <bits/stdc++.h> using namespace std; typedef long long ll; int read() { int x;scanf("%d",&x);return x; } int main() { // freopen("1.in","r",stdin); // freopen("3.out","w",stdout); int a=read(),b=read(); if(a==2||b==1) printf("-"); else if(a==0) printf("biii"); else printf("dudu"); puts(""); if(a==1) printf("move"); else printf("stop"); }
4 模拟,数学题
用now表示当前,前i项全能的最少人数,则刚开始now=n,每次循环,拿着a[i]来更新now,公式是now=max(0,now+a-n)
#include <bits/stdc++.h> using namespace std; typedef long long ll; int read() { int x;scanf("%d",&x);return x; } int n,m,now; int main() { // freopen("1.in","r",stdin); // freopen("3.out","w",stdout); now=n=read(); m=read(); for(int i=1;i<=m;i++) { int a=read(); now=max(0,now+a-n); } cout<<now; }
5 模拟。字符串操作
根据样例5,需要先枚举n个违禁词,再枚举每个位置,进行替换。
判断可以循环或者用substr。替换可以用replace函数。
为了避免"<censored>"的字串是某个违禁词,比如有个违禁词叫cen。可以每次替换后写一个i=i+10;并且替换时使用"\1\1\1\1\1\1\1\1\1\1"这个串而非"<censored>",避免违禁词再次被替换。
#include <bits/stdc++.h> using namespace std; typedef long long ll; int read() { int x;scanf("%d",&x);return x; } string cen="<censored>",s[110],t,tt="\1\1\1\1\1\1\1\1\1\1"; int n,ans; void work(int &i,int j) { if(i+s[j].size()>t.size()){ i++; return ; } for(int k=0;k<s[j].size();k++) { if(t[i+k]!=s[j][k]){ i++; return ; } } ans++; t.replace(i,s[j].size(),tt); i=i+10; } int main() { // freopen("1.in","r",stdin); // freopen("3.out","w",stdout); cin>>n; for(int i=1;i<=n;i++) cin>>s[i]; int k; cin>>k; getline(cin,t); getline(cin,t); for(int j=1;j<=n;j++) for(int i=0;i<t.size();) work(i,j); if(ans<k) { for(int i=0;i<t.size();i++) if(t[i]=='\1') t.replace(i,cen.size(),cen); cout<<t; } else cout<<ans<<"\nHe Xie Ni Quan Jia!"; }
6 模拟,统计
用一个sum存买了几个,ans存多少钱即可。
#include <bits/stdc++.h> using namespace std; typedef long long ll; int read() { int x;scanf("%d",&x);return x; } int n,sum[110],x,y; double ans,a[110]; int main() { // freopen("1.in","r",stdin); // freopen("3.out","w",stdout); n=read(); for(int i=1;i<=n;i++) scanf("%lf",&a[i]); while(scanf("%d%d",&x,&y)!=-1) { sum[x]+=y; ans=ans+y*a[x]; } for(int i=1;i<=n;i++) cout<<sum[i]<<'\n'; printf("%.2lf",ans); }
7 搜索,数学,模拟
考虑先写一个dfs询问每个数字的持续性。然后用map套vector存所有持续性的数字。则答案是map里first最大的那些数字,输出一下。
#include <bits/stdc++.h> using namespace std; typedef long long ll; int read() { int x;scanf("%d",&x);return x; } int ask(int x) { int t=1; do { t=t*(x%10); x=x/10; }while(x); return t; } int dfs(int x) { if(ask(x)==x) return 0; return dfs(ask(x))+1; } int a,b; map<int,vector<int>>o; int main() { // freopen("1.in","r",stdin); // freopen("3.out","w",stdout); a=read();b=read(); for(int i=a;i<=b;i++) o[dfs(i)].push_back(i); cout<<o.rbegin()->first<<'\n'; auto x=o.rbegin()->second; cout<<x[0]; for(int i=1;i<x.size();i++) cout<<' '<<x[i]; }
8 模拟
考虑用一个check(set<int>a)来判断1~9是否恰好在set里出现一次,剩下的任务就是枚举每行每列9个3*3即可。
#include <bits/stdc++.h> using namespace std; typedef long long ll; int read() { int x;scanf("%d",&x);return x; } int v[10][10]; int check(set<int>a) { for(int i=1;i<=9;i++) if(a.count(i)==0) return 0; return 1; } int check(int x) { set<int>a,b; for(int i=1;i<=9;i++) a.insert(v[x][i]),b.insert(v[i][x]); return check(a)&check(b); } int check(int x,int y) { set<int>a; for(int i=0;i<3;i++) for(int j=0;j<3;j++) a.insert(v[i+x][j+y]); return check(a); } void work() { for(int i=1;i<=9;i++) for(int j=1;j<=9;j++) v[i][j]=read(); for(int i=1;i<=9;i++) if(check(i)==0) { cout<<"0\n"; return ; } for(int i=1;i<=9;i+=3) for(int j=1;j<=9;j+=3) if(check(i,j)==0) { cout<<"0\n"; return ; } cout<<"1\n"; } int main() { // freopen("1.in","r",stdin); // freopen("3.out","w",stdout); for(int t=read();t;t--) work(); }
L2
1 mao套bitset
为了解决兼得,我一眼想到了bitset。f[x]表示对于物品x,n个人的拥有情况。写好后交了一发,发现数组开小了,开大点却空间炸了。
仔细一看,q只有100,所以有用的bitset最多只有200个,考虑整一个flag,把原来的bitset<100010>f[100010]改成map<int,bitset<100010>>f即可
#include <bits/stdc++.h> using namespace std; typedef long long ll; int read() { int x;scanf("%d",&x);return x; } int n,m,q; int flag[100010],x[110],y[110]; map<int,bitset<100010>>f; vector<int>a[100010]; int main() { // freopen("1.in","r",stdin); // freopen("3.out","w",stdout); n=read();m=read(); for(int i=1;i<=n;i++) for(int t=read();t;t--) a[i].push_back(read()); q=read(); for(int i=1;i<=q;i++) { x[i]=read();y[i]=read(); flag[x[i]]=flag[y[i]]=1; } for(int i=1;i<=n;i++) for(auto v:a[i]) if(flag[v]) f[v][i]=1; for(int i=1;i<=q;i++) cout<<(f[x[i]]&f[y[i]]).count()<<'\n'; }
2 map套vector
首先实现一个把s的首字母提取出来得到t的函数
然后可以发现,可以用map套vector存某个前缀t的所有句子。读入时先存进去,输出时去找对应的map即可
为了解决格式错误,需要特判提取s首字母的时候,如果s开头是空格,则不能把开头放进去。
为了解决字典序,可以遍历一下map,把vector排个序。
#include <bits/stdc++.h> using namespace std; typedef long long ll; int read() { int x;scanf("%d",&x);return x; } string s,t; int n,q; map<string,vector<string>>o; void work() { t.clear(); if(s[0]!=' ') t=t+s[0]; for(int i=1;i<s.size();i++) if(s[i-1]==' '&&s[i]!=' ') t=t+s[i]; } int main() { cin>>n; getline(cin,s); for(int i=1;i<=n;i++) { getline(cin,s); work(); o[t].push_back(s); } for(auto &x:o) sort(x.second.begin(),x.second.end()); cin>>q; getline(cin,s); for(int i=1;i<=q;i++) { getline(cin,s); work(); if(o.count(t)==0) cout<<s<<'\n'; else { cout<<o[t][0]; for(int i=1;i<o[t].size();i++) cout<<'|'<<o[t][i]; cout<<'\n'; } } }
3 dfs
最喜欢的建图方式,直接e[fa].push_back(i)即可,dfs的时候也不需要检查fa
考虑建图,存边,得到最大的度数。
第一问输出最大的度数
第二问判断是否所有的度数=0或者等于最大的度数
第三问输出dfs序即可,为了字典序,需要跑之前sort一下边
#include <bits/stdc++.h> using namespace std; typedef long long ll; int read() { int x;scanf("%d",&x);return x; } int root,n,ans; vector<int>e[100010]; void dfs(int x) { if(x==root) cout<<x; else cout<<' '<<x; sort(e[x].begin(),e[x].end()); for(auto y:e[x]) dfs(y); } int main() { // freopen("1.in","r",stdin); // freopen("3.out","w",stdout); n=read(); for(int i=1;i<=n;i++) { int x=read(); if(x==0) root=i; else e[x].push_back(i); } for(int i=1;i<=n;i++) ans=max(ans,(int)e[i].size()); cout<<ans<<' '; for(int i=1;i<=n;i++) if(ans!=(int)e[i].size()&&0!=e[i].size()) ans=-1; if(ans==-1) cout<<"no\n"; else cout<<"yes\n"; dfs(root); }
4 动态规划
考虑f[i][x1][x2]表示当前在第i行,第1列和为x1,第2列和为x2的方案数,转移一下即可。
x1+x2需要等于i*l才有意义,所以可以省去一维,但是空间开的下也就不管他了。
#include <bits/stdc++.h> using namespace std; typedef long long ll; int read() { int x;scanf("%d",&x);return x; } int n,l; int f[3][60][60],g[4][60][60][60],h[5][60][60][60][60]; int main() { // freopen("1.in","r",stdin); // freopen("3.out","w",stdout); l=read();n=read(); if(n==2) { f[0][0][0]=1; for(int i=1;i<=n;i++) { for(int x1=0;x1<=l;x1++) { int x2=l-x1; for(int y1=0;y1<=(i-1)*l;y1++) { int y2=(i-1)*l-y1; f[i][x1+y1][x2+y2]=f[i][x1+y1][x2+y2]+f[i-1][y1][y2]; } } } cout<<f[n][l][l]; } else if(n==3) { g[0][0][0][0]=1; for(int i=1;i<=n;i++) { for(int x1=0;x1<=l;x1++) { for(int x2=0;x2<=l;x2++) { int x3=l-x1-x2; if(x3<0)break; for(int y1=0;y1<=(i-1)*l;y1++) { for(int y2=0;y2<=(i-1)*l;y2++) { int y3=(i-1)*l-y1-y2; if(y3<0)break; g[i][x1+y1][x2+y2][x3+y3]=g[i][x1+y1][x2+y2][x3+y3]+g[i-1][y1][y2][y3]; } } } } } cout<<g[n][l][l][l]; } else if(n==4) { h[0][0][0][0][0]=1; for(int i=1;i<=n;i++) { for(int x1=0;x1<=l;x1++) { for(int x2=0;x2<=l;x2++) { for(int x3=0;x3<=l;x3++) { int x4=l-x1-x2-x3; if(x4<0)break; for(int y1=0;y1<=(i-1)*l;y1++) { for(int y2=0;y2<=(i-1)*l;y2++) { for(int y3=0;y3<=(i-1)*l;y3++) { int y4=(i-1)*l-y1-y2-y3; if(y4<0)break; h[i][x1+y1][x2+y2][x3+y3][x4+y4]=h[i][x1+y1][x2+y2][x3+y3][x4+y4]+h[i-1][y1][y2][y3][y4]; } } } } } } } cout<<h[n][l][l][l][l]; } }
本题也可搜索+剪枝或者搜索+打表解决。
L3
1 bfs最短路,排序
首先写一个从大本营的最短路
则一个队伍火拼失去资格,当然是存在多个队伍最短路和他相等。于是可以用sort结构体排序一下,判断左边右边是否和他最短路相等。
如果最短路=0x3f3f3f3f也要失去资格,可以给k+1赋值最短路为0x3f3f3f3f来解决,这样所有的0x3f3f3f3f也会被判失去资格(continue)
#include <bits/stdc++.h> using namespace std; typedef long long ll; int read() { int x;scanf("%d",&x);return x; } int n,m,k,x,y,nx,ny,d[110][110],a[110][110]; int dx[]={1,-1,0,0},dy[]={0,0,1,-1}; queue<pair<int,int>>q; struct node { int i,t; friend bool operator <(node a,node b) { return a.t<b.t; } }o[10010]; int main() { // freopen("1.in","r",stdin); // freopen("3.out","w",stdout); n=read();m=read(); memset(d,0x3f,sizeof(d)); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { a[i][j]=read(); if(a[i][j]==2) x=i,y=j; } q.push({x,y}); d[x][y]=0; while(q.size()) { x=q.front().first; y=q.front().second; q.pop(); for(int i=0;i<4;i++) { nx=x+dx[i]; ny=y+dy[i]; if(nx==0||ny==0||nx==n+1||ny==m+1||d[nx][ny]<=d[x][y]+1||a[nx][ny]==0) continue; d[nx][ny]=d[x][y]+1; q.push({nx,ny}); } } k=read(); for(int i=1;i<=k;i++) { y=read(),x=read(); o[i].i=i; o[i].t=d[x][y]; } sort(o+1,o+1+k); o[1+k].t=0x3f3f3f3f; for(int i=1;i<=k;i++) { if(o[i-1].t==o[i].t||o[i+1].t==o[i].t||o[i].t==0x3f3f3f3f) continue; cout<<o[i].i<<' '<<o[i].t; return 0; } cout<<"No winner."; }
2 二分,前缀和
拿到题一眼二分,但是写了会发现不好实现,于是决定先写一个暴力。
考虑使用一个ti往外枚举,贪心地选取。
#include <bits/stdc++.h> using namespace std; typedef long long ll; int read() { int x;scanf("%d",&x);return x; } int n,m,k; char s[500010]; ll ask(int x) { ll tsum=0; int tk=k,tm=m,ti=1,i; if(s[x]=='1') tk--; else if(tm) tk--,tm--; for(;ti<=2*n&&tm&&tk;ti++) { if(ti&1) { i=ti/2+1; if(x-i<1)continue; if(s[x-i]=='1') tk--,tsum+=i; else tk--,tm--,tsum+=i; } else { i=ti/2; if(x+i>n)continue; if(s[x+i]=='1') tk--,tsum+=i; else tk--,tm--,tsum+=i; } } for(;ti<=2*n&&tk;ti++) { if(ti&1) { i=ti/2+1; if(x-i<1)continue; if(s[x-i]=='1') tk--,tsum+=i; } else { i=ti/2; if(x+i>n)continue; if(s[x+i]=='1') tk--,tsum+=i; } } return tsum; } void work() { n=read();m=read();k=read(); scanf("%s",s+1); cout<<ask(1); for(int i=2;i<=n;i++) cout<<' '<<ask(i); cout<<'\n'; } int main() { // freopen("1.in","r",stdin); // freopen("3.out","w",stdout); for(int t=read();t;t--) work(); }
然后我拿了19分,看不出问题就去写前面的分了。写完前面的分回来又想了想拿了21。
再想一想,可以发现这个ti可以拿来二分。首先输出暴力代码中的每个ti。
然后开始修改代码,使得二分出来的和他的一样。
最后算一下贡献。
#include <bits/stdc++.h> using namespace std; typedef long long ll; int read() { int x;scanf("%d",&x);return x; } int n,m,k,sum1[200010],sum0[200010]; ll sum[200010]; char s[200010]; void ask(int x,int ti,int &l,int &r) { if(ti&1) l=max(1,x-ti/2-1),r=min(n,x+ti/2); else l=max(1,x-ti/2),r=min(n,x+ti/2); } ll ask(ll x) { int tk=k,tm=m,ti,i; if(s[x]=='1') tk--; else if(tm) tk--,tm--; int L=0,R=2*n,MID,l,r; while(L+1<R) { MID=(L+R)/2; ask(x,MID,l,r); if(sum0[r]-sum0[l-1]-(s[x]=='0')>=tm||r-l>=tk) R=MID; else L=MID; } ask(x,L,l,r); if(sum0[r]-sum0[l-1]-(s[x]=='0')<tm) L=R; R=2*n; int l0,r0; ask(x,L,l0,r0); ll tsum=(1ll+x-l0)*(x-l0)/2+(1ll+r0-x)*(r0-x)/2; tk=tk-(r0-l0); while(L+1<R) { MID=(L+R)/2; ask(x,MID,l,r); if(sum1[r]-sum1[r0]+sum1[l0-1]-sum1[l-1]>=tk) R=MID; else L=MID; } ask(x,L,l,r); if(sum1[r]-sum1[r0]+sum1[l0-1]-sum1[l-1]<tk) ask(x,R,l,r); return tsum+(sum[r]-sum[r0])-(sum1[r]-sum1[r0])*x+x*(sum1[l0-1]-sum1[l-1])-sum[l0-1]+sum[l-1]; } void work() { n=read();m=read();k=read(); scanf("%s",s+1); for(int i=1;i<=n;i++) { sum1[i]=sum1[i-1]+(s[i]=='1'); sum0[i]=sum0[i-1]+(s[i]=='0'); sum[i]=sum[i-1]+(s[i]=='1')*i; } cout<<ask(1); for(int i=2;i<=n;i++) cout<<' '<<ask(i); cout<<'\n'; } int main() { // freopen("1.in","r",stdin); // freopen("1.out","w",stdout); for(int t=read();t;t--) work(); }