题目库链接:团体程序设计天梯赛-练习集
NEXT:PTA天梯练习赛 刷题笔记2 ;
OP
\
L2-001 紧急救援 (25 分)
dijkstra(迪杰斯特拉)算法
大佬说SPFA也可以做,mark一下,接下来学
思路
可以参考这里 来了解迪杰斯特拉的基本原理;
此题的主要特定是最短路可能有多种规划路线,而最优解只有一个,即需要同时处理距离和人数两个维度的信息;
主要思想是将所有点分为已达点与未达点两个集合(代码中用一个数组进行01标记),最开始时,已达点集合仅有出发点,未达点集合包含其余所有点;
主要过程是每一次循环遍历所有未达点,对于每一个未达点 pj ,如果可以通过一条道路(长度 ki )连接至已达点 pi ,则记录由此未达点到达初始点的最短距离(设 pi 点到起始点的最短距离 li ,l j = m i n ( l i + k i ) l_j=min(l_i+k_i) l j = m i n ( l i + k i ) ),并记录所有能使 lj 取最小值的 i ,作为到达此点的方案数;
在所有未达点中选择距离初始点最短的点,将其加入已达点集合,并在所有可行方案( i )中选择积累人数最多的点作为最终与其连接的点,新点的累计人数即为与其连接点的累积人数和新点原有人数的和;
如此循环,直到目标点已达;
最后按顺序找出整个路径上的各个点并输出即可。
详见代码注释
进行人数存储的部分可优化,即smp变量不必要
时间复杂度O(n3 )
代码
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 #include <bits/stdc++.h> using namespace std;typedef long long ll;const ll M=1e9 +7 ;struct { vector<int >fr; int strd=100000000 ; int p=0 ; int smp=0 ; }cty[502 ];int main () { int n,m,f,t,a,b,l; int r[502 ][502 ]={0 },rch[502 ]={0 }; cin>>n>>m>>f>>t; cty[f].fr.push_back (f); rch[f]=1 ; cty[f].strd=0 ; for (int i=0 ;i<n;i++) { cin>>cty[i].p; cty[i].smp=cty[i].p; } for (int i=0 ;i<m;i++) { cin>>a>>b>>l; if (r[a][b])r[a][b]=min (r[a][b],l); else r[a][b]=l; if (r[b][a])r[b][a]=min (r[b][a],l); else r[b][a]=l; } while (!rch[t]) { int mnr=100000000 -8 ,mnm=-1 ; for (int i=0 ;i<n;i++) { if (!rch[i]) { cty[i].fr.clear (); cty[i].strd=100000000 ; for (int j=0 ;j<n;j++) { if (rch[j]&&r[i][j]) { if (cty[j].strd+r[i][j]<cty[i].strd) { cty[i].strd=cty[j].strd+r[i][j]; cty[i].fr.clear (); cty[i].fr.push_back (j); } else if (cty[j].strd+r[i][j]==cty[i].strd) { cty[i].fr.push_back (j); } } } if (cty[i].strd<mnr) { mnr=cty[i].strd; mnm=i; } } } int mxp=0 ,mxm=-1 ; for (int j:cty[mnm].fr) { rch[mnm]+=rch[j]; if (cty[j].smp>mxp) { mxp=cty[j].smp; mxm=j; } } cty[mnm].fr.clear (); cty[mnm].fr.push_back (mxm); cty[mnm].smp+=cty[mxm].smp; } printf ("%d %d\n" ,rch[t],cty[t].smp); stack<int >stk; int now=t; stk.push (t); stk.push (cty[now].fr[0 ]); now=cty[now].fr[0 ]; while (stk.top ()!=f) { stk.push (cty[now].fr[0 ]); now=cty[now].fr[0 ]; } printf ("%d" ,stk.top ()); stk.pop (); while (stk.size ()) { printf (" %d" ,stk.top ()); stk.pop (); } return 0 ; }
L2-002 链表去重 (25 分)
数据结构
思路
存住上一个节点,按要求更新处理即可;
要握住两个链表的起始点;
代码
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 #include <bits/stdc++.h> using namespace std;typedef long long ll;const ll M=1e9 +7 ;struct { int num; int nxt; }vtr[100005 ];int main () { int f1,n,g,f2=-1 ; cin>>f1>>n; for (int i=1 ;i<=n;i++) { cin>>g; cin>>vtr[g].num>>vtr[g].nxt; } int now=f1,a[10004 ]={0 },lst=0 ,now2; while (now!=-1 ) { if (a[abs (vtr[now].num)]) { vtr[lst].nxt=vtr[now].nxt; if (f2==-1 )f2=now,now2=now,now=vtr[now].nxt,vtr[now2].nxt=-1 ; else { vtr[now2].nxt=now; now2=now; now=vtr[now].nxt; vtr[now2].nxt=-1 ; } } else { a[abs (vtr[now].num)]=1 ; lst=now; now=vtr[now].nxt; } } now=f1; while (now!=-1 ) { printf ("%05d %d " ,now,vtr[now].num); if (vtr[now].nxt==-1 )printf ("%d\n" ,-1 ); else printf ("%05d\n" ,vtr[now].nxt); now=vtr[now].nxt; } now=f2; while (now!=-1 ) { printf ("%05d %d " ,now,vtr[now].num); if (vtr[now].nxt==-1 )printf ("%d\n" ,-1 ); else printf ("%05d\n" ,vtr[now].nxt); now=vtr[now].nxt; } return 0 ; }
L2-003 月饼 (25 分)
模拟,贪心
思路
模拟即可,优先卖单价高的;
代码
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 #include <bits/stdc++.h> using namespace std;typedef long long ll;const ll M=1e9 +7 ;struct mooncake { double w; double m; }mnk[1003 ];bool cmp (const mooncake a,const mooncake b) { return 1.0 *a.m/a.w>1.0 *b.m/b.w; }int main () { int t; double n; cin>>t>>n; for (int i=1 ;i<=t;i++)cin>>mnk[i].w; for (int i=1 ;i<=t;i++)cin>>mnk[i].m; sort (mnk+1 ,mnk+t+1 ,cmp); double s=0 ; for (int i=1 ;i<=t&&n;i++) { if (n>=mnk[i].w) { n-=mnk[i].w; s+=mnk[i].m; } else { s+=1.0 *n*mnk[i].m/mnk[i].w; n=0 ; } } printf ("%.2lf" ,s); return 0 ; }
L2-004 这是二叉搜索树吗? (25 分)
树的前序遍历
思路
对于题目中描述的二叉搜索树,满足以下性质:
对于一棵树前序遍历 [ l , r ] ,第一个元素必为根节点;
设从第二个节点开始,第一个大于等于根节点的节点编号为mk,m a x [ l , m k − 1 ] < r o o t , m i n [ m k , r ] < = r o o t max [ l , mk - 1 ]<root,min[ mk , r ]<=root m a x [ l , m k − 1 ] < r o o t , m i n [ m k , r ] < = r o o t ;
镜像树类似;
代码
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 #include <bits/stdc++.h> using namespace std;typedef long long ll;const ll M=1e9 +7 ; queue<int >que;int a[1003 ];bool ju (int l,int r,int dir) { int i,root=a[l],ans=1 ,mk=r+1 ,mi=root,mx=root-1 ; if (dir) { for (i=l+1 ;i<=r;i++) { if (a[i]>=root)mk=min (mk,i); if (mk==r+1 )mx=max (mx,a[i]); else mi=min (mi,a[i]); } if (mk!=l+1 &&mx>=root||mk!=r+1 &&mi<root) { return 0 ; } } else { for (i=l+1 ;i<=r;i++) { if (a[i]<root)mk=min (mk,i); if (mk==r+1 )mi=min (mi,a[i]); else mx=max (mx,a[i]); } if (mk!=r+1 &&mx>=root||mk!=l+1 &&mi<root) { return 0 ; } } if (mk!=l+1 ) { ans*=ju (l+1 ,mk-1 ,dir); } if (mk!=r+1 ) { ans*=ju (mk,r,dir); } que.push (root); return ans; }int main () { int n,i,f=1 ; cin>>n; for (i=1 ;i<=n&&f;i++)cin>>a[i]; { if (a[1 ]>a[2 ])f=ju (1 ,n,1 ); else f=ju (1 ,n,0 ); if (f) { printf ("YES\n" ,que.size ()); while (!que.empty ()) { printf ("%d" ,que.front ()); if (que.size ()!=1 )printf (" " ); que.pop (); } } else printf ("NO" ); } return 0 ; }
L2-005 集合相似度 (25 分)
双指针
思路
之前写过一篇题解,和这个题很像,详见关于 相似的数集 的思路+时间复杂度分析+代码 ;
排序后双指针去重即可,时间复杂度 O(KN);
经测试,用map桶计数来查总数会TLE;
代码
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 #include <bits/stdc++.h> using namespace std;typedef long long ll;const ll M=1e9 +7 ;int main () { int t,n,i,j; int a[51 ][2003 ]={0 }; cin>>t; for (i=1 ;i<=t;i++) { cin>>a[i][0 ]; for (j=1 ;j<=a[i][0 ];j++) { scanf ("%d" ,&a[i][j]); } sort (a[i]+1 ,a[i]+a[i][0 ]+1 ); } cin>>n; int x,y,cc,ca; while (n--) { cc=0 ; cin>>x>>y; ca=a[x][0 ]+a[y][0 ]; i=j=1 ; while (i<=a[x][0 ]&&j<=a[y][0 ]) { while (a[x][i+1 ]==a[x][i])i++,ca--; while (a[y][j+1 ]==a[y][j])j++,ca--; if (a[x][i]<a[y][j])i++; else if (a[x][i]>a[y][j])j++; else cc++,i++,j++,ca--; } printf ("%.2lf%%\n" ,100.0 *cc/ca); } return 0 ; }
L2-006 树的遍历 (25 分)
树的遍历
思路
思路与代码可以移步我之前的题解 关于 树的遍历 一题的思路+代码(树的后序与中序遍历输出层序遍历)(递归) ;
补充:这个方法太麻烦了,下面的 L2-011 玩转二叉树 用的方法简单些
代码
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 #include <bits/stdc++.h> using namespace std;typedef long long ll; vector<int > son[31 ];int n,i,j;int mid[31 ],lst[31 ],rot=0 ;void maketr (int l,int r,int fa) { if (l==r) { son[fa].push_back (mid[l]); return ; } else if (l>r)return ; int root=0 ,rm=0 ; for (i=l;i<=r;i++) { for (j=1 ;j<=n;j++) { if (mid[i]==lst[j]) { if (j>root){root=j,rm=i;} break ; } } } if (!rot)rot=lst[root]; son[fa].push_back (lst[root]); maketr (l,rm-1 ,lst[root]); maketr (rm+1 ,r,lst[root]); return ; } queue<int > que;void bfs (int x) { int pf=0 ; for (i=0 ;i<son[x].size ();i++)que.push (son[x][i]); while (!que.empty ()) { int now=que.front (); que.pop (); if (!pf)pf=1 ; else printf (" " ); printf ("%d" ,now); for (i=0 ;i<son[now].size ();i++)que.push (son[now][i]); } }int main () { cin>>n; for (i=1 ;i<=n;i++)scanf ("%d" ,&lst[i]); for (i=1 ;i<=n;i++)scanf ("%d" ,&mid[i]); maketr (1 ,n,0 ); bfs (0 ); return 0 ; }
L2-007 家庭房产 (25 分)
并查集
思路
基础思想是并查集,不过需要注意的细节很多;
可以将每一个家庭编号最小的成员作为家庭的根成员;
详见注释
代码
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 #include <bits/stdc++.h> using namespace std;typedef long long ll;const ll M=1e9 +7 ;struct { int t,s; int p; int f; }ppo[10000 ];struct fmy { double tpp,spp; int p; int mn; }fm[10000 ];int find (int x) { if (ppo[x].f==x)return x; ppo[x].f=find (ppo[x].f); return ppo[x].f; }bool cmp (const fmy a,const fmy b) { if (fabs (a.spp-b.spp)<=0.00001 ) { return a.mn<b.mn; } return a.spp>b.spp; }int main () { int n,i,x,g,m,c=0 ,get[10000 ]={0 }; cin>>n; for (i=0 ;i<=9999 ;i++)ppo[i].f=i; while (n--) { cin>>x; int fx,fg; if (!get[x])ppo[find (x)].p++,get[x]=1 ; m=2 ; while (m--) { cin>>g; if (g==-1 )continue ; if (!get[g])ppo[find (g)].p++,get[g]=1 ; fx=find (x),fg=find (g); if (fg>fx) { ppo[fg].f=fx; ppo[fx].p+=ppo[fg].p; ppo[fx].s+=ppo[fg].s; ppo[fx].t+=ppo[fg].t; } else if (fg<fx) { ppo[fx].f=fg; ppo[fg].p+=ppo[fx].p; ppo[fg].s+=ppo[fx].s; ppo[fg].t+=ppo[fx].t; } } cin>>m; while (m--) { cin>>g; if (g==-1 )continue ; if (!get[g])ppo[find (g)].p++,get[g]=1 ; fx=find (x),fg=find (g); if (fg>fx) { ppo[fg].f=fx; ppo[fx].p+=ppo[fg].p; ppo[fx].s+=ppo[fg].s; ppo[fx].t+=ppo[fg].t; } else if (fg<fx) { ppo[fx].f=fg; ppo[fg].p+=ppo[fx].p; ppo[fg].s+=ppo[fx].s; ppo[fg].t+=ppo[fx].t; } } fx=find (x); cin>>g; ppo[fx].t+=g; cin>>g; ppo[fx].s+=g; } for (i=0 ;i<=9999 ;i++) { if (find (i)==i&&ppo[i].p) { fm[c].tpp=ppo[i].t*1.0 /ppo[i].p; fm[c].spp=ppo[i].s*1.0 /ppo[i].p; fm[c].p=ppo[i].p; fm[c].mn=i; c++; } } printf ("%d\n" ,c); sort (fm,fm+c,cmp); for (i=0 ;i<c;i++) { printf ("%04d %d %.3lf %.3lf\n" ,fm[i].mn,fm[i].p,fm[i].tpp,fm[i].spp); } return 0 ; }
L2-008 最长对称子串 (25 分)
哈希字符串
思路
从两个方向进行字符串哈希,枚举所有区间,并比对;
代码中 ri[ i ] 存储的即为字符串前i位的哈希值;
le[ i ] 存储字符串后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 #include <bits/stdc++.h> using namespace std;typedef long long ll;const ll M=1e9 +7 ,P=131 ;ll qm (ll a,ll b) { ll ans=1 ; for (;b;b>>=1 ) { if (b&1 )ans=ans*a%M; a=a*a%M; } return ans; }void makehashr (string &g,ll *r,int len) { for (int i=1 ;i<=len+1 ;i++) { r[i]=r[i-1 ]*P+g[i]; r[i]%=M; } }void makehashl (string &g,ll *r,int len) { for (int i=1 ;i<=len+1 ;i++) { r[i]=r[i-1 ]*P+g[len+1 -i]; r[i]%=M; } }ll gethash1 (ll *p,int l,int r) { return (p[r]+M-(p[l-1 ]*qm (P,r-l+1 ))%M)%M; }int main () { string g; ll ri[1003 ]={0 },le[1003 ]={0 }; getline (cin,g); g="a" +g; g[0 ]=0 ; int len=strlen (&g[1 ]); makehashr (g,ri,len); makehashl (g,le,len); int mi=1 ; for (int i=1 ;i<=len;i++) for (int j=i+mi;j<=len;j++) { if (gethash1 (ri,i,j)==gethash1 (le,len-j+1 ,len-i+1 ))mi=max (mi,j-i+1 ); } cout<<mi; return 0 ; }
L2-009 抢红包 (25 分)
结构体排序
思路
第一反应是用map存盈亏情况,结果发现还需要依照若干依据排序,就结构体啦;
处理好cmp函数即可;
代码
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 #include <bits/stdc++.h> using namespace std;typedef long long ll;struct pp { int w; int c; int i; }p[10004 ];bool cmp (const pp a,const pp b) { if (a.w==b.w) { if (a.c==b.c) { return a.i<b.i; } return a.c>b.c; } return a.w>b.w; }int main () { int n,t,g1,g2,i; cin>>t; for (i=1 ;i<=t;i++) { p[i].i=i; int s=0 ; cin>>n; while (n--) { cin>>g2>>g1; s+=g1; p[g2].w+=g1; p[g2].c++; } p[i].w-=s; } sort (p+1 ,p+t+1 ,cmp); for (i=1 ;i<=t;i++) { printf ("%d %.2lf\n" ,p[i].i,p[i].w*1.0 /100 ); } return 0 ; }
L2-010 排座位 (25 分)
并查集
思路
依照题意,只需要使用并查集来存储朋友关系即可,敌人关系用map直接存就行;
需要注意的是共同的朋友
指在他们的朋友网 中有相同的人,而不是直接朋友 中有相同的人;
总人数很少,可以直接遍历寻找;
代码
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 #include <bits/stdc++.h> using namespace std;typedef long long ll; map<int ,int >fr[102 ];int fa[102 ];int find (int x) { if (fa[x]==x)return x; fa[x]=find (fa[x]); return fa[x]; }int main () { int n,m,k,a,b,o; int i; cin>>n>>m>>k; for (i=1 ;i<=n;i++)fa[i]=i; for (i=1 ;i<=m;i++) { cin>>a>>b>>o; if (o==1 ) { fa[find (a)]=find (b); fr[a][b]=1 ; fr[b][a]=1 ; } else { fr[a][b]=-1 ; fr[b][a]=-1 ; } } for (i=1 ;i<=k;i++) { cin>>a>>b; if (fr[a][b]==-1 ) { int j; for (j=1 ;j<=n;j++) if (find (a)==find (j)&&find (b)==find (j))break ; if (j==n+1 )printf ("No way\n" ); else printf ("OK but...\n" ); } else if (fr[a][b]==1 ||find (a)==find (b))printf ("No problem\n" ); else if (fr[a][b]==0 )printf ("OK\n" ); } return 0 ; }
L2-011 玩转二叉树 (25 分)
树的遍历
思路
看到这道题就想起来前面的 树的遍历 一题,那个题的大体想法是用一个队列按层序线性存树,非常麻烦,需要单独dfs,此处要求输出镜像树,那么把每一层分别用栈存即可;
同理,对于上题,每层用队列分别存储即可;
对于任意子树的中序遍历,有,在前序遍历中位置最靠前的节点为该子树的根;
已知的数据特性:
——n!=0;
——节点编号<=999,不保证节点编号<=n;
段错误了好久,都因为是想当然…
代码
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 #include <bits/stdc++.h> using namespace std;typedef long long ll;int m[41 ],inf[1000 ]; stack<int >stk[40 ];void bdtr (int l,int r,int c) { int mi=40 ,mk=-1 ; for (int i=l;i<=r;i++) { if (inf[m[i]]<mi)mi=inf[m[i]],mk=i; } { stk[c].push (m[mk]); if (mk>l)bdtr (l,mk-1 ,c+1 ); if (mk<r)bdtr (mk+1 ,r,c+1 ); } }int main () { int n,g; cin>>n; for (int i=1 ;i<=n;i++)scanf ("%d" ,&m[i]); for (int i=1 ;i<=n;i++)scanf ("%d" ,&g),inf[g]=i; bdtr (1 ,n,0 ); if (stk[0 ].size ())printf ("%d" ,stk[0 ].top ()),stk[0 ].pop (); for (int i=1 ;i<=39 ;i++) { if (stk[i].empty ())break ; while (stk[i].size ()) { printf (" %d" ,stk[i].top ()); stk[i].pop (); } } return 0 ; }
L2-012 关于堆的判断(25分)
堆,字符串
思路
关于堆的基本原理,可以参考这里 ;
堆的特性决定了节点 i 的父节点是 i/2,左子节点是 i*2,右子节点是 i*2+1,由此我们就可以快速判断节点间相互关系;
之后建堆,处理字符串即可;
代码
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 #include <bits/stdc++.h> using namespace std;typedef long long ll;int hp[1003 ];int c=1 ; map<int ,int >mp;void bdhp (int x) { int now=c; c++; hp[now]=x; while (now>1 &&hp[now]<hp[now/2 ]) { swap (hp[now],hp[now>>1 ]); now>>=1 ; } }int main () { int n,m,ge,i,a,b; string g; cin>>n>>m; for (i=1 ;i<=n;i++) { scanf ("%d" ,&ge); bdhp (ge); } for (i=1 ;i<c;i++)mp[hp[i]]=i; while (m--) { cin>>a; cin>>g; if (g=="and" ) { cin>>b; printf ("%c\n" ,mp[a]/2 ==mp[b]/2 ?'T' :'F' ); cin>>g; cin>>g; } else { cin>>g; cin>>g; if (g=="root" )printf ("%c\n" ,mp[a]==1 ?'T' :'F' ); else if (g=="parent" ) { cin>>g; cin>>b; printf ("%c\n" ,mp[b]/2 ==mp[a]?'T' :'F' ); } else if (g=="child" ) { cin>>g; cin>>b; printf ("%c\n" ,mp[a]/2 ==mp[b]?'T' :'F' ); } } } return 0 ; }
ED
\