PTA天梯训练赛一&二
训练赛一
7-5 连续因子 (20 分)
思路:
暴力枚举起点,每次从起点往后延伸,并且更新长度和起点,最后输出即可。
代码:
int main(){
ll n;scanf("%lld",&n);
ll res=0,pos=-1;
for(ll i=2;i*i<=n+1;i++){
ll tmp=0;
ll tmpn=n,t=i;
for(;tmpn%t==0;t++,tmp++)
tmpn/=t;
if(res<tmp){
res=tmp;
pos=i;
}
}
if(res==0){
cout<<"1"<<"\n"<<n<<"\n";
return 0;
}
cout<<res<<endl;
for(int i=1;i<=res;i++){
cout<<pos+i-1;
if(i!=res) cout<<"*";
}
puts("");
return 0;
}
7-6 链表去重 (25 分)
思路:
之前存图的时候用链式前向星,这里也可以借助数组模拟链表,然而我写的暴力只过了15分。
代码:
const int maxn=1e5+100;
bool vis[maxn];
int w[maxn],ne[maxn],del_w[maxn],del_ne[maxn];
int n,head=-1,pre=-1,del_head=-1,del_tail=-1;
int main(){
head=read,n=read;
for(int i=1;i<=n;i++){
int pos=read;
w[pos]=read;ne[pos]=read;
}
for(int i=head;~i;i=ne[i]){
if(vis[abs(w[i])]){
if(pre==-1) head=ne[i];
else ne[pre]=ne[i];
if(del_head==-1) del_head=i;
del_w[i]=w[i];
if(del_tail!=-1) del_ne[del_tail]=i;
del_tail=i;
}
else{
vis[abs(w[i])]=1;
pre=i;
}
}
del_ne[del_tail]=-1;
for(int i=head;~i;i=ne[i]){
printf("%05d %d ",i,w[i]);
if(ne[i]==-1) puts("-1");
else printf("%05d\n",ne[i]);
}
for(int i=del_head;~i;i=del_ne[i]){
printf("%05d %d ",i,del_w[i]);
if(del_ne[i]==-1) puts("-1");
else printf("%05d\n",del_ne[i]);
}
return 0;
}
7-8 凑零钱 (30 分)
思路:
背包dp判断能否构成的同时记录路径,输出答案的时候倒着输出。
dfs也能过。
代码:
const int maxn=1e4+100;
int n,m,a[maxn],dp[maxn];
int vis[maxn][maxn];
bool cmp(int a,int b){
return a>b;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++)
for(int j=m;j>=0;j--){
if(j>=a[i]){
if(dp[j]<=dp[j-a[i]]+a[i]){
dp[j]=dp[j-a[i]]+a[i];
vis[i][j]=1;
}
}
}
if(dp[m]!=m) puts("No Solution");
else{
int tmpm=m;
while(tmpm){
if(vis[n][tmpm]){
cout<<a[n];
tmpm-=a[n];
if(tmpm>0) cout<<" ";
}
n--;
}
}
return 0;
}
7-9 特殊堆栈 (30 分)
思路:
有点思维的题。在动态求中位数时,一般都是用对顶堆维护。但是对顶堆的删除操作不容易实现。
先考虑暴力的做法,是维护一个栈和一个vector容器,每次询问时都对vector进行sort,这样复杂度是
O
(
n
2
)
O(n^{2})
O(n2)
如果在插入的时候就保持vector的有序,每次查询时都二分查找该数在有序列表中的位置,时间复杂度就降到了
O
(
n
l
o
g
n
+
c
)
O(nlogn+c)
O(nlogn+c)
代码:
const int maxn=1e5+100;
stack<int>stk;
vector<int>v;
int main(){
int n;cin>>n;
char op[15];
for(int i=1;i<=n;i++){
cin>>op;
///cout<<op<<endl;
if(op[1]=='o'){
if(v.size()==0) puts("Invalid");
else{
int x=stk.top();
stk.pop();
cout<<x<<endl;
auto pos=lower_bound(v.begin(),v.end(),x);
v.erase(pos);
}
}
else if(op[1]=='u'){
int x;cin>>x;
stk.push(x);
auto pos=lower_bound(v.begin(),v.end(),x);
v.insert(pos,x);
}
else if(op[1]=='e') {
if(v.size()==0) puts("Invalid");
else{
int t=(v.size()+1)/2-1;
cout<<v[t]<<endl;
}
}
cout<<i<<" "<<op<<endl;
}
return 0;
}
7-11 肿瘤诊断 (30 分)
思路:
三维的bfs,有点离谱,注意判断每个连通块的最低1的个数。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
struct node{
int x,y,z;
};
int a[60][1300][130],n,m,l,t,b[60][1300][130],res=0;
bool check(int x,int y,int z){
if(x>=0&&x<l&&y>=0&&y<m&&z>=0&&z<n&&a[x][y][z]==1&&b[x][y][z]==0)
return 1;
return 0;
}
int nx[]={1,-1,0,0,0,0};
int ny[]={0,0,1,-1,0,0};
int nz[]={0,0,0,0,1,-1};
void bfs(int x,int y,int z){
queue<node>q;
q.push({x,y,z});
b[x][y][z]=1;
int tmp=0;
while(!q.empty()){
node t=q.front();q.pop();
tmp++;
int tx=t.x,ty=t.y,tz=t.z;
for(int i=0;i<6;i++){
int xx=tx+nx[i],yy=ty+ny[i],zz=tz+nz[i];
if(check(xx,yy,zz)){
b[xx][yy][zz]=1;
q.push({xx,yy,zz});
}
}
}
if(tmp>=t) res+=tmp;
}
int main(){
cin>>m>>n>>l>>t;
for(int i=0;i<l;i++)
for(int j=0;j<m;j++)
for(int k=0;k<n;k++)
cin>>a[i][j][k];
for(int i=0;i<l;i++)
for(int j=0;j<m;j++)
for(int k=0;k<n;k++)
if(a[i][j][k]==1&&!b[i][j][k])
bfs(i,j,k);
cout<<res<<endl;
return 0;
}
训练赛二
7-4 天梯地图 (30 分)
思路:
跑两次最短路。
对于时间来说,时间最短为第一关键字,距离最短为第二关键字。
对于距离来说,距离最短为第一关键字,节点最少为第二关键字。
用pre数组记录一下这个点是从哪个点转移过来的,输出的时候倒着推回去。
判断两个路径是否相同,先判断节点个数,在枚举节点判断对应的是否相等。
n只有500,写个朴素的dijkstra就好了
也不知道被卡哪了。
代码:
const int maxn=1e6+7,inf=0x3f3f3f3f;
int n,m,g1[510][510],g2[510][510],s,e;
int dis1[maxn],st1[maxn],pre1[maxn],cnt[maxn];
int dis2[maxn],st2[maxn],pre2[maxn],d[maxn];
void dijkstra1(){
memset(dis1,0x3f,sizeof dis1);
dis1[s]=0;
pre1[s]=-1;
for(int i=1;i<=n;i++) cnt[i]=1;
for(int i=0;i<n-1;i++){
int t=-1;
for(int j=1;j<=n;j++)
if(!st1[j]&&(t==-1||dis1[t]>dis1[j]))
t=j;
for(int j=1;j<=n;j++){
if(dis1[j]>dis1[t]+g1[t][j]){
dis1[j]=dis1[t]+g1[t][j];
pre1[j]=t;
cnt[j]=cnt[t]+1;
}
else if(dis1[j]==dis1[t]+g1[t][j]){
if(cnt[j]>cnt[t]+1){
pre1[j]=t;
cnt[j]=cnt[t]+1;
}
}
}
st1[t]=1;
}
}
void dijkstra2(){
memset(dis2,0x3f,sizeof dis2);
memset(d,0x3f,sizeof d);
dis2[s]=0;d[s]=0;
pre2[s]=-1;
for(int i=0;i<n-1;i++){
int t=-1;
for(int j=1;j<=n;j++)
if(!st2[j]&&(t==-1||dis2[t]>dis2[j]))
t=j;
for(int j=1;j<=n;j++){
if(dis2[j]>dis2[t]+g2[t][j]){
dis2[j]=dis2[t]+g2[t][j];
pre2[j]=t;
d[j]=d[t]+g1[t][j];
}
else if(dis2[j]==dis2[t]+g2[t][j]){
if(d[j]>d[t]+g1[t][j]){
pre1[j]=t;
d[j]=d[t]+g1[t][j];
}
}
}
st2[t]=1;
}
}
int main(){
n=read,m=read;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i==j) g1[i][j]=g2[i][j]=0;
else g1[i][j]=g2[i][j]=inf;
rep(i,1,m){
int u=read,v=read,op=read,l=read,t=read;
u++,v++;
if(op==1){
g1[u][v]=min(g1[u][v],l);
g2[u][v]=min(g2[u][v],t);
}
else{
g1[u][v]=min(g1[u][v],l);
g2[u][v]=min(g2[u][v],t);
g1[v][u]=min(g1[v][u],l);
g2[v][u]=min(g2[v][u],t);
}
}
s=read,e=read;
s++,e++;
dijkstra1();
dijkstra2();
int res_time=dis2[e],res_dis=dis1[e];
string path_time,path_dis;
int tmp=e;
while(tmp!=-1){
path_time+=(tmp-1+'0');
tmp=pre2[tmp];
}
tmp=e;
while(tmp!=-1){
path_dis+=(tmp-1+'0');
tmp=pre1[tmp];
}
reverse(path_dis.begin(), path_dis.end());
reverse(path_time.begin(), path_time.end());
if(path_dis==path_time){
printf("Time = %d; Distance = %d: ",res_time,res_dis);
for(int i=0;i<path_dis.size();i++){
cout<<path_dis[i];
if(i!=path_dis.size()-1) cout<<" => ";
}
}
else{
printf("Time = %d: ",res_time);
for(int i=0;i<path_time.size();i++){
cout<<path_time[i];
if(i!=path_time.size()-1) cout<<" => ";
}
puts("");
printf("Distance = %d: ",res_dis);
for(int i=0;i<path_dis.size();i++){
cout<<path_dis[i];
if(i!=path_dis.size()-1) cout<<" => ";
}
}
return 0;
}
7-9 家庭房产 (25 分)
思路:
思路不难想,就是并查集,但是有点难处理。
每次将根节点维护为家族里编号最小的编号,剩下的就是模拟了。
代码:
const int maxn=11000;
int root[maxn],siz[maxn];
struct node{
ll minid,siz,id;
double cnt,area;
}a[1100];
int cnt_sum[maxn],cnt_area[maxn];
bool vis[maxn];
int Find(int x){
if(x!=root[x]) root[x]=Find(root[x]);
return root[x];
}
void Union(int x,int y){
x=Find(x),y=Find(y);
if(x!=y){
if(x<y){
root[y]=x;
siz[x]+=siz[y];
cnt_area[x]+=cnt_area[y];
cnt_sum[x]+=cnt_sum[y];
}
else{///y的编号小
root[x]=y;
siz[y]+=siz[x];
cnt_area[y]+=cnt_area[x];
cnt_sum[y]+=cnt_sum[x];
}
}
}
bool cmp(node a,node b){
if(a.area==b.area){
return a.id<b.id;
}
return a.area>b.area;
}
int main(){
for(int i=0;i<=9999;i++) root[i]=i,siz[i]=1;
int n=read;
rep(i,1,n){
int now=read;
vis[now]=1;
int fa=read,mo=read;
if(fa!=-1){
Union(fa,now);
vis[fa]=1;
}
if(mo!=-1){
vis[mo]=1;
Union(mo,now);
}
int k=read;
rep(j,1,k){
int son=read;
vis[son]=1;
Union(son,now);
}
int t=Find(now);
ll t1=read,t2=read;
cnt_sum[t]+=t1;
cnt_area[t]+=t2;
}
int cnt=0;
for(int i=0;i<=9999;i++)
if(vis[i]&&i==Find(i)){
a[++cnt]={i,siz[i],cnt,cnt_sum[i]*1.0/siz[i],cnt_area[i]*1.0/siz[i]};
}
sort(a+1,a+1+cnt,cmp);
cout<<cnt<<endl;
for(int i=1;i<=cnt;i++)
printf("%04lld %lld %.3f %.3f\n",a[i].minid,a[i].siz,a[i].cnt,a[i].area);
return 0;
}
7-10 最长对称子串 (25 分)
思路:
有好几种解法:
1.马拉车,复杂度
O
(
n
)
O(n)
O(n)
2.二分对称子串的长度,复杂度
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
3.枚举对称点,每次从对称点往周围延伸,记录最大长度。跟马拉车的思想类似,复杂度
O
(
n
2
)
O(n^{2})
O(n2)
4.枚举起点和终点,复杂度
O
(
n
3
)
O(n^{3})
O(n3),但是跑不满,应该也能过。
代码:
const int maxn= 3000+10;
char str[maxn];
string s;
int len1,len2,res,p[maxn];
void init(){
str[0]='$';str[1]='#';
for(int i=0;i<len1;i++){
str[i*2+2]=s[i];
str[i*2+3]='#';
}
len2=len1*2+2;
str[len2]='*';
}
void manacher(){
int id=0,mx=0;
for(int i=1;i<len2;i++){
if(mx>i) p[i]=min(p[id*2-i],mx-i);
else p[i]=1;
for(;str[i+p[i]]==str[i-p[i]];p[i]++);
if(p[i]+i>mx){
mx=p[i]+i;
id=i;
}
}
}
int main(){
getline(cin,s);
len1=s.size();
init();
manacher();
int res=0;
for(int i=1;i<len2;i++)
res=max(res,p[i]);
cout<<res-1;
return 0;
}
7-11 垃圾箱分布 (30 分)
思路:
将垃圾箱的编号调整到
[
n
+
1
,
n
+
m
]
[n+1,n+m]
[n+1,n+m]
m最大为10,对每个垃圾箱都跑一遍最短路,记录最短距离,平均距离,编号,sort后输出。
代码:
const int maxn=1e6+7,inf=0x3f3f3f3f;
int n,m,k,ds,g[1100][1100];
struct node{
int id,minn;
double ave;
}a[12];
int idx;
bool cmp(node a,node b){
if(a.minn==b.minn){
if(a.ave==b.ave){
return a.id<b.id;
}
return a.ave<b.ave;
}
return a.minn>b.minn;
}
int dis[maxn];///记录当前点到起点的距离
bool st[maxn];///若当前点的最短距离已经确定 为true
void dfs(int s){
memset(dis,0x3f,sizeof dis);///初始化距离为正无穷
memset(st,0,sizeof st);
dis[s]=0;///从1开始更新
for(int i=0;i<n+m-1;i++){
int t=-1;
///找到当前不在st中而且距离最小的点t
for(int j=1;j<=n+m;j++)
if(!st[j]&&(t==-1||dis[t]>dis[j]))
t=j;
///用点t更新其他不在st中的点的距离
for(int j=1;j<=n+m;j++)
dis[j]=min(dis[j],dis[t]+g[t][j]);
///将t点放到st集合里
st[t]=true;
}
int res=inf,cnt=0,sum=0;
for(int i=1;i<=n;i++){
if(dis[i]>ds){
break;
}
sum+=dis[i];cnt++;
res=min(res,dis[i]);
}
if(cnt==n){
idx++;
a[idx]={s,res,sum*1.0/n};
}
}
int main(){
n=read,m=read,k=read,ds=read;
for(int i=1;i<=n+m;i++)
for(int j=1;j<=n+m;j++)
if(i==j) g[i][j]=0;
else g[i][j]=inf;
for(int i=1;i<=k;i++){
string x,y;cin>>x>>y;
int t=read;
int cntx=0,cnty=0;
if(x[0]=='G'){
for(int j=1;j<x.size();j++)
cntx=cntx*10+x[j]-'0';
cntx+=n;
}
else{
for(int j=0;j<x.size();j++)
cntx=cntx*10+x[j]-'0';
}
if(y[0]=='G'){
for(int j=1;j<y.size();j++)
cnty=cnty*10+y[j]-'0';
cnty+=n;
}
else{
for(int j=0;j<y.size();j++)
cnty=cnty*10+y[j]-'0';
}
/// cout<<i<<"******"<<cntx<<" "<<cnty<<endl;
g[cntx][cnty]=g[cnty][cntx]=min(t,g[cntx][cnty]);
}
for(int i=n+1;i<=n+m;i++)
dfs(i);
/// cout<<idx<<endl;
if(idx==0) puts("No Solution");
else{
sort(a+1,a+1+idx,cmp);
cout<<"G"<<a[1].id-n<<endl;
printf("%.1lf %.1lf\n",1.0*a[1].minn,a[1].ave);
}
return 0;
}