2017年12月励志杯月赛 题解
来源:互联网 发布:史书 知乎 编辑:程序博客网 时间:2024/06/15 17:01
T1 中位数(median.cpp)
【题目描述】
有一个长度为N的数列{A1,A2,…An},这N和数字恰好是1..N的一个排列。你需要统计有多少个子序列{Ai,Ai+1,…Aj}满足:i<=j且j-i+1为奇数,序列的中位数为B。例如{5,1,3}的中位数为3。
【输入文件】
第一行包含两个正整数N和B;第二行包含N个整数,第i个整数为Ai。
【输出文件】
仅包含一个整数,为满足条件的子序列的个数。
【样例输入】
7 4
5 7 2 4 3 1 6
【样例输出】
4
【数据规模】
对于30%的数据中,满足N<=100;
对于60%的数据中,满足N<=1000;
对于100%的数据中,满足N<=100000,1<=B<=N。
【题解】【规律+递推】
【因为B是序列的中位数,所以B一定存在于序列中,所以,记录B在序列中的位置num,并把相对应的数置为0;把小于B的数置为-1;大于B的数置为1。】
【分别从num向左向右扫描,并分别设一个累加器,将枚举时遇到的0、1、-1都加入累加器里,并分别用L、R数组记录累加器中每个数出现的次数】
【最后求:∑L[i]*R[-i](但因为C++没有负数下标,所以要整体向右移n个下标)】
#include<cstdio>#include<cstring>#include<algorithm>using namespace std;int a[100010],L[200010],R[200010],n,b,num,ans;int main(){ freopen("median.in","r",stdin); freopen("median.out","w",stdout); int i,j; scanf("%d%d",&n,&b); for(i=1;i<=n;++i) { scanf("%d",&a[i]); if(a[i]==b) {num=i; a[i]=0; continue;} if(a[i]>b) a[i]=1; else a[i]=-1; } int l=n,r=n; for(i=num;i>0;--i) l+=a[i],L[l]++; for(i=num;i<=n;++i) r+=a[i],R[r]++; int len=2*n; for(i=0;i<=len;++i) ans+=(L[i]*R[len-i]); printf("%d\n",ans); return 0;}
一、敲砖块
【题目描述】
在一个凹槽中放置了N层砖块,最上面的一层油N块砖,从上到下每层一次减少一块砖。每块砖都有一个分支,敲掉这块砖就能得到相应的分值,如图所示。
如果你想敲掉第i层的第j块砖的话,若i=1,你可以直接敲掉它;若i>1,则你必须先敲掉第i-1层的第j和第j+1块砖。
你现在可以敲掉最多M块砖,求得分最多能有多少。
【输入文件】
输入文件的第一行有两个正整数N和M;
接下来的N行,描述这N层砖块上的分值A[i,j],满足0<=A[i,j]<=100。
【输出文件】
仅一行,包含一个整数,为最大的得分。
【样例输入】
4 5
2 2 3 4
8 2 7
2 3
49
【样例输出】
19
【数据规模】
对于20%的数据,满足1<=N<=10,1<=M<=30
对于100%的数据,满足1<=N<=50,1<=M<=500。
题解:
首先我们将砖块向左对齐,变成一个直角三角形的形式,可以发现:
1. 每一列必须敲掉从第一行开始的从上到下的若干个砖块
2.如果某一列敲掉了K个砖块,那么其右边的那一列至少敲掉了K-1个砖块。
所以我们可以dp。
f [ i ] [ j ] [ k ] 表示从右到左已经敲到了第I列,其中第I列敲掉了J个砖块且还剩K个砖块所得到的最大价值。
f[i][j][k]=max(f[i+1][v][k+j])+a[1][i]+..+a[1]=j-1>j
#include<cstdio>#include<cstring>#include<iostream>using namespace std;int n,m,a[510][510],fi,fj,fk,v[510][510],f[60][60][510],ans; int main(){ freopen("brike.in","r",stdin); freopen("brike.out","w",stdout); scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) for (int j=1;j<=n-i+1;j++) { scanf("%d",&a[i][j]); } for (int i=n;i>=1;i--) for (int j=1;j<=n-i+1;j++) v[i][j]=v[i][j-1]+a[j][i]; memset(f,-1,sizeof(f)); f[n+1][0][m]=0; for (int j=n;j>=1;j--) for (int i=0;i<=n-j+1;i++) for (int k=m-i;k>=0;k--) { for (int l=i-1;l<=n-j+1;l++) if (f[j+1][l][k+i]!=-1) f[j][i][k]=max(f[j][i][k],f[j+1][l][k+i]+v[j][i]); if (ans<f[j][i][k]) { ans=f[j][i][k]; } } printf("%d",ans); }
T3 邮递员送信
【问题描述】
有一个邮递员要送东西,邮局在节点1.他总共要送N-1样东西,其目的地分别是2~N。由于这个城市的交通比较繁忙,因此所有的道路都是单行的,共有M条道路,通过每条道路需要一定的时间。这个邮递员每次只能带一样东西。求送完这N-1样东西并且最终回到邮局最少需要多少时间。
【样例输入】
5 102 3 51 5 53 5 61 2 81 3 85 3 44 1 84 5 33 5 65 4 2
【样例输出】
83
【题解 思路1】
题解:大水题,考试想到了两遍最短路,却没有继续向深层思考。只要将边反过来,那之前是1到2~n个点的最短路,反过来就是2~n个点到1的最短路,两遍dijkstra便可。同时要注意题目中有重边出现,用邻接矩阵注意判重,这也显示出了邻接表的优势,邻接表不需要判重。
#include<cmath>#include<cstdio>#include<iostream>#include<cstring>using namespace std;int n,m,maxx=1e9,minn,t,z,i,j,k,x,y;long long ans;bool flag[1006];int c[1006],d[1006];int f1[1006][1006],f2[1006][1006];int get(){ int x=0,p=1; char c; c=getchar(); while (c<'0'||c>'9') {if (c=='-') p=-1;c=getchar();} while (c>='0'&&c<='9') {x=x*10+c-'0';c=getchar();} return x*p;}int main(){ n=get();m=get(); memset(f1,127/3,sizeof(f1)); memset(f2,127/3,sizeof(f2)); memset(c,127/3,sizeof(c)); memset(d,127/3,sizeof(d)); maxx=f1[0][0]; for (i=1;i<=m;i++) { x=get();y=get();z=get(); if (f1[x][y]!=maxx) f1[x][y]=min(f1[x][y],z); else f1[x][y]=z; if (f2[y][x]!=maxx) f2[y][x]=min(f2[y][x],z); else f2[y][x]=z; } memset(flag,false,sizeof(flag)); for (i=1;i<=n;i++) c[i]=f1[1][i]; flag[1]=true; c[1]=0; for (i=1;i<=n-1;i++) { minn=maxx; k=0; for (j=1;j<=n;j++) if ((!flag[j])&&(c[j]<minn)) { minn=c[j]; k=j; } if (k==0) break; flag[k]=true; for (j=1;j<=n;j++) { if (c[k]+f1[k][j]<c[j]) c[j]=c[k]+f1[k][j]; } } memset(flag,false,sizeof(flag)); for (i=1;i<=n;i++) d[i]=f2[1][i]; flag[1]=true; d[1]=0; for (i=1;i<=n-1;i++) { minn=maxx; k=0; for (j=1;j<=n;j++) if ((!flag[j])&&(d[j]<minn)) { minn=d[j]; k=j; } if (k==0) break; flag[k]=true; for (j=1;j<=n;j++) { if (d[k]+f2[k][j]<d[j]) d[j]=d[k]+f2[k][j]; } } for (i=1;i<=n;i++) ans+=c[i]+d[i]; printf("%lld",ans); }
【题解】【最短路】
【这道题思路十分精妙,因为是有向图,所以刚开始按数据建图,跑一遍SPFA;再把所有边反向,再跑一遍SPFA。把两次的最短路都加入答案】
#include<queue>#include<cstdio>#include<cstring>#include<algorithm>#define ll long longusing namespace std;int f[1010][1010];int a[100010],nxt[100010],p[1010],val[100010],tot;int n,m;ll ans,dis[1010];inline void add(int x,int y,int v){ tot++; a[tot]=y; nxt[tot]=p[x]; p[x]=tot; val[tot]=v;}inline void spfa(){ queue<int>que; memset(dis,127/3,sizeof(dis)); dis[1]=0; que.push(1); while(!que.empty()) { int u=que.front(); que.pop(); int v=p[u]; while(v!=-1) { if(dis[a[v]]>dis[u]+(ll)val[v]) { dis[a[v]]=dis[u]+(ll)val[v]; que.push(a[v]); } v=nxt[v]; } } for(int i=2;i<=n;++i) ans+=dis[i]; return;}int main(){ freopen("post.in","r",stdin); freopen("post.out","w",stdout); int i,j; memset(f,127,sizeof(f)); memset(p,-1,sizeof(p)); memset(nxt,-1,sizeof(nxt)); scanf("%d%d",&n,&m); for(i=1;i<=m;++i) { int x,y,z; scanf("%d%d%d",&x,&y,&z); if(f[x][y]>z) f[x][y]=z; add(x,y,z); } spfa(); tot=0; memset(p,-1,sizeof(p)); memset(nxt,-1,sizeof(nxt)); for(i=1;i<=n;++i) for(j=1;j<=n;++j) if(i!=j&&f[i][j]!=f[0][0]) add(j,i,f[i][j]); spfa(); printf("%lld\n",ans); return 0;}