这篇以及接下来的几篇博客,会记录我在寒假集训中做到的题的一些想法和思路,一来帮助我更好的进行知识架构,二来可以让我理清思路并更好的掌握相关知识点;
A-跳石头
一年一度“跳石头”比赛又要开始了!
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 N 块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 M 块岩石(不能移走起点和终点的岩石)。
输入格式
输入第一行包含三个整数 L,N,M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。
接下来 N 行,每行一个整数,第 i 行的整数 Di(0 < Di < L)Di(0<Di<L) 表示第 i 块岩石与 起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。
输出格式
输出只包含一个整数,即最短跳跃距离的最大值。
数据范围
对于 20% 的数据,0≤M≤N≤10。
对于 50% 的数据, 0≤M≤N≤100。
对于 100% 的数据, 0≤M≤N≤50,000,1≤L≤1,000,000,000。
这一道题,它求的是选手在比赛过程中的最大的最短跳跃距离,并且所给的数字都是整数,想要直接求解的话无从下手,因为如果要先搬石头再计算最短距离的话,必须先搬的是当前的最小距离的那两块石头之一,因此会用到排序,考虑到M最大有50,000,即最多可能进行这么多次排序,即使快排也力不从心。那怎么办?求极限无能为力的时候就夹逼,这里也可以用先找答案的想法,就是把结果猜出来再判断是否正确,怎么猜呢?这就要用到二分查找了。
二分查找:有时候也称为“折半查找”,它是把原序列划分成元素个数尽量接近的两个子序列,即逐步缩小范围,然后递归查找,但一般使用迭代实现。二分查找只适用于有序序列,时间复杂度为O(logn)。
回到题目,在跳石头这里,我们可以在0~L的范围内二分查找最短距离X,然后验算这个最短距离是否符合即可。那怎么验算呢? 只需要遍历每个石头之间的距离,若小于X,则把后面的一个石头“搬走”,否则继续遍历,直到最后,判断搬走的石头数量是不是少于N即验算完成。
贴代码:
1 #include<cstdio> 2 int d[50001], l,n, m; 3 bool check(int dis) 4 { 5 int s=0, last=0; 6 for(int i = 1; i <= n; i++) 7 { 8 if (d[i] - last >= dis)last = d[i]; 9 else s++;//计算搬走的石头的数量10 } 11 12 return s <= m;// dis 太大则返回false 13 } 14 15 int binarysearch(int L, int R)//二分查找 16 { 17 int l = L, r = R, ans = 0; 18 int mid=r+l>>1;//mid就是二分查找的当前最短距离 19 while(l <= r) { 20 mid = (l + r) >> 1; 21 if(check(mid)) {ans = mid; l = mid+1;} 22 else r = mid-1; 23 } 24 return ans; 25 } 26 27 int main() 28 { 29 scanf("%d%d%d", &l, &n, &m); 30 for (int i = 1; i <= n; i++)scanf("%d", &d[i]); 31 int result = binarysearch(0, l); 32 printf("%d", result); 33 }
B-Strange Function
Input
第一行包含一个整数T(1<=T<=100) ,代表测试样例的个数。接下来输入包含T行,每行一个实数y,代表当前学长游戏人物的生命值(0 < y <1e10)。
Output
输出一行包含一个实数F(x)(0 <= x <=100),代表最低水位,保留4位小数
这道题题目挺短的,问的也很直接,就是求一个高次函数的最小值,而且函数里面还有需要输入的y;这种题目一般使用估算。对F(x)导一遍以后发现,F'(0)<0,存在x,使F'(x)>0,因此最小值在0~100内取得,所以就可以在这个区间内进行二分查找,找到令F'(x)=0的x,对应的F(x)就是最小值。当然也可以直接进行三分查找F(x)的最小值,只不过三分查找也可以转化为二分来实现。
所以,对这道题,直接对F'(x)=0对应的x的值在0~100范围内进行二分搜索,然后代入F(x)即可;不过介于x值无法精确表示出来,而且答案也说了保留4位小数,因此需要用一个很小的值eps来进行估算, 一般让它等于1e-6,其实只要满足误差够小就行了。
贴代码:
#include<cstdio> #include<cmath> double eps=1e-6; double x,y; long double f(double x)//F(x)的导数 { return 42*pow(x,6)+48*pow(x,5)+21*x*x+10*x-y; } long double F(double x) { return 6*pow(x,7)+8*pow(x,6)+7*pow(x,3)+5*x*x-y*x; } int main() { int T; scanf("%d",&T); for(int k=0;k<T;k++) { scanf("%lf",&y); double l=0.0,r=100.0; while(r-l>eps)//在x的误差小于eps的前提下二分搜索f(x)=0对应的x值 { double mid=(r+l)/2; if(f(mid)<0)l=mid;//二分搜索的核心 else r=mid; } printf("%.4lf\n",F(r));//这里用l和r都一样 } }
C-Saruman's Army
yd马上就要去打比赛了,所以最近一直在训练,这可委屈了他可爱的女朋友。所以yd和女朋友玩了一个这样的游戏:在一条直线上有N个糖果。第i个糖果的位置是X[i]。从这N个糖果中选择若干个,把他们标记起来。对于每一个糖果,在和它本身相距为R的区域内必须要有标记的糖果(本身带有标记的糖果,就可以认为和它相距为0的地方有一个糖果被标记)。在满足这个条件的情况,最后如果有a个糖果被标记,yd就要陪他女朋友玩a小时,但yd还要训练,他想让这个a最小。
Input
输入的测试文件将包含多个样例。 每个测试样例第一行有两个数据,整数R(其中0≤R≤1000)和整数N(其中1≤N≤1000)。 下一行包含N个整数,指示每个糖果的位置X[1],…,X[N](其中0≤X[i]≤1000)。当R==N==-1时,输入结束。
Output
对于每组输入数据,输出一个数,代表yd需要陪女朋友所要用的最少时间
题目意思很直接,就是要用最少的标记,把所有的点都在标记的点的R半径范围内,所以这道题用贪心的思路:先把所有的点排序,从第一个点开始,在R半径内最右边的那个点做上标记,然后从这个标记的点开始,R半径范围内最右边的那个点的下一个点又开始前面的操作,知道所有的点都在标记的范围内即可。
所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,它所做出的仅仅是在某种意义上的局部最优解。
贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。
下面贴代码:
1 #include <iostream> 2 #include<algorithm> 3 using namespace std; 4 int main() 5 { 6 int r,n,a[1001],ans=0; 7 while(cin>>r>>n&&(r!=-1&&n!=-1)) 8 { 9 ans=0; 10 for(int i=0;i<n;i++)cin>>a[i]; 11 sort(a,a+n);//要先排序 12 int i=0; 13 while(i<n) 14 { 15 int s=a[i];//从这个点开始 16 while(i<n&&a[i]-s<=r)i++;//找到r半径范围内最右边的点进行标记 17 int p=a[i-1]; 18 while(i<n&&a[i]-p<=r)i++;//找到标记点右边r范围内最右边的点的下一个点 19 ans++;//上面的操作标记了一个点 20 } 21 cout<<ans<<endl; 22 } 23 }
D-Best Cow Line
最近彩虹岛上发生了一个可怕的事件,大魔王ZZY为了督促彩虹岛的岛民好好训练,把每个人的电子设备都给加了一个密码,英勇的彩虹岛岛民要和大魔王作斗争,于是迫使大魔王说出了一个长度为N(1≤N≤2000)的字符串S,然而最后的密码是由S中所有字母构成字典序最小的字符串T(起初T是一个空串)。但是要想知道最后的密码只能以下两种操作:
·从S的头部删除一个字符,加到T的尾部
·从S的尾部删除一个字符,加到T的尾部
目标是要构造字典序尽可能小的字符串
Input
第一行一个整数N(代表字符串S的长度) 接下来的2~N+1行是字符串S中的字母(只包含大写字母)
Output
输出时每行最多80个字符
Sample Input
6 A C D B C B
Sample Output
ABCBCD
这也是一道贪心的题目,为了让我们看懂题目出题人把原题改成这个样子也是脑洞真大挺不容易;思路很简单,比较字符串首尾两个元素,取小的一个放入空串,如果相等的话,就比较里面的一对,如果还是相等则再往里比较,直到找到不相等的一对或到循环结束为止。
贴代码:
1 #include<iostream> 2 #include<cstring> 3 using namespace std; 4 int main() 5 { 6 char a[2002]; 7 char b[2002]; 8 int n; 9 cin>>n; 10 for(int i=1;i<=n;i++)cin>>a[i]; 11 int l=1,r=n,cnt=1; 12 while(l<=r) 13 { 14 bool s=false; 15 for(int i=0;l+i<=r;i++)//如果首尾相等会一直循环 16 { 17 if(a[l+i]<a[r-i]){s=true;break;} 18 else if(a[l+i]>a[r-i]){s=false;break;} 19 } 20 if(s)b[cnt++]=a[l++];//把小的一个放入空串 21 else b[cnt++]=a[r--]; 22 } 23 for(int i=1;i<=n;i++) 24 { 25 cout<<b[i]; 26 if(i%80==0)cout<<endl;//不要漏了题目要求 27 } 28 }
E - Fence Repair
比如:举神和ww把重量为3和5的石子堆合并,可以得到一个重量为8的石子堆,但同时需要消耗3+5的体力值。
现在他两想把n堆石子合并到只剩下一堆,但他们又不想消耗太多体力,你能帮他们求出消耗的最少体力吗? 数据范围:n<=20000
Input
接下来n行,每行一个整数a[i](1<=a[i]<=50000)代表开始时每堆石子的重量
Output
又是一道贪心的题目,我们先设想,每次都合并最小的两堆是最优子解,假如先搬了不是最轻的一堆,这一堆在后面会重复被搬起,总共消耗的体力必然比重复搬最轻的一堆要多,所以我们只要每次合并最轻的两堆就可以得到消耗的最少体力。
贴代码:
1 #include<iostream> 2 using namespace std; 3 #include<algorithm> 4 #include<queue> 5 #include<cstdio> 6 7 int main() 8 { 9 priority_queue<int,vector<int>,greater<int> >q;//用从小到大排序的优先队列 10 int n,t; 11 cin>>n; 12 for(int i=0;i<n;i++) 13 { 14 cin>>t; 15 q.push(t);//把每堆石子的重量放入队列 16 } 17 long long s=0,sum=0;//s是合并了一堆消耗的体力,sum是总共消耗的体力 18 while(q.size()>1)//队列里面多于一堆石子就继续 19 { 20 s=0; 21 s+=q.top();//把最轻的一堆加上,再pop掉,因为它以及被搬过了 22 q.pop(); 23 s+=q.top();//再加最轻的一堆并pop掉,这里完成了两堆的合并 24 q.pop(); 25 q.push(s);//把合并的两堆再放入队列,因为不知道它是不是最小的一堆 26 sum+=s;//每搬两堆就把消耗的体力加入sum 27 } 28 cout<<sum<<endl; 29 }
F-Cable Master
To buy network cables, the Judging Committee has contacted a local network solutions provider with a request to sell for them a specified number of cables with equal lengths. The Judging Committee wants the cables to be as long as possible to sit contestants as far from each other as possible.
The Cable Master of the company was assigned to the task. He knows the length of each cable in the stock up to a centimeter,and he can cut them with a centimeter precision being told the length of the pieces he must cut. However, this time, the length is not known and the Cable Master is completely puzzled.
You are to help the Cable Master, by writing a program that will determine the maximal possible length of a cable piece that can be cut from the cables in the stock, to get the specified number of pieces.
Input
Output
If it is not possible to cut the requested number of pieces each one being at least one centimeter long, then the output file must contain the single number "0.00" (without quotes).
这道题我wa到自闭了,先是二分输出的结果错误,又被卡精度。
题意大概是:在给出的n条网线里面,剪出k条尽量长的网线,我们要计算的就是能达到最长的网线的长度。因为长度是升序的,所以用二分来检索答案,把每条网线除于“答案”的和与k比较就可以验算答案是否正确。
答案要输出的是结果有小数点后两位,这里直接用.2lf是不对的,因为这会导致四舍五入,比如结果是1.156,答案应该是1.15,但却得到1.16,所以我们先用把结果*100,得到115.6,再用floor()得到115,再除回100就行了。
贴代码:
#include<iostream> #include<cstdio> #include<cmath> using namespace std; const double eps=1e-6;//这里用1e-3也能过,因为结果精度要求到小数点后两位 double a[10005]; int n,k; int check(double s) { int sum=0; for(int i=0;i<n;i++)sum+=int(a[i]/s);//int不可以少,因为需要的是可以获得的网线的数量 return sum; } int main() { cin>>n>>k; double x=0.0; for(int i=0;i<n;i++) { cin>>a[i]; x+=a[i]; } double l=0.0,r=x/k;//最大长度肯定小于x/k,我一开始直接用100000其实也没毛病,只是多二分了几次 while(l+eps<r) { double mid=l+(r-l)/2; if(check(mid)>=k)l=mid; else r=mid; } printf("%.2lf\n",floor(r*100)/100);//我一来就用l结果wa上天,要求的是最大的长度,应该用r,自己多试几次就可以理解了。 }
G-逆序对
给定一个1-N的排列A1, A2, ... AN,如果Ai和Aj满足i < j且Ai > Aj,我们就称(Ai, Aj)是一个逆序对。
求A1, A2 ... AN中所有逆序对的数目。
Input
第一行包含一个整数N。
第二行包含N个两两不同整数A1, A2, ... AN。(1 <= Ai <= N)
对于60%的数据 1 <= N <= 1000
对于100%的数据 1 <= N <= 100000
Output
一个整数代表答案
求逆序对有几种方法,暴力O(n^2),归并排序,树状数组。暴力的话肯定T,树状数组比较复杂,还没太搞懂,所以我在这里用归并来做了(当然这也是命题人的意图),因为归并在这里刚学到。
归并排序算是一种高效且稳定的排序方法,时间复杂度是O(n*logn),和快排相近,关于它的介绍我就引用书本了:
归并排序按照分治三步法:划分问题:把序列分成元素个数尽量相等的两半;
递归求解:把两半元素分别排序;
合并问题:把两个有序序列合并成一个;
那怎么在归并排序的时候求逆序对呢?求逆序对的过程在合并有序序列的时候完成。在两个有序序列中,左边的数的下标必然小于右边的数,如果左边的数x(从左往右)比右边第一个数y要大,那么左边比x要大的所有数都大于y,这一次比较就增加了左边的数的个数个逆序对。归并排序完成后,所有的逆序对也就算出来了。
贴代码:
1 #include<iostream> 2 using namespace std; 3 const int N = 1e5+5; 4 int a[N],b[N]; 5 long long sum;//逆序对的个数最大会爆int 6 void MergeSort(int l,int r) 7 { 8 if(l==r)return;//递归终止条件 9 int mid=r+l>>1; 10 MergeSort(l,mid);MergeSort(mid+1,r); 11 int cnt=l,h1=l,h2=mid+1; 12 while(h1<=mid&&h2<=r)//合并两个排好序的子序列 13 { 14 if(a[h1]<=a[h2])b[cnt++]=a[h1++]; 15 else {b[cnt++]=a[h2++];sum+=mid-h1+1;}//a[h2]比较小的时候就出现了逆序对,且个数是mid-h1+1; 16 } 17 while(h1<=mid)b[cnt++]=a[h1++];//因为可能有些数没被放进b里面,这两行不能漏掉 18 while(h2<=r)b[cnt++]=a[h2++]; 19 for(int i=l;i<=r;i++)a[i]=b[i];//最后要把排序好的b替换回a 20 } 21 int main() 22 { 23 int n; 24 cin>>n; 25 for(int i=1;i<=n;i++)cin>>a[i]; 26 MergeSort(1,n); 27 //for(int i=1;i<=n;i++)cout<<a[i]<<" "; 28 cout<<sum; 29 }
H - 排名
每题的分值,所以并不是最后的排名。给定录取分数线,请你写程序找出最后通过分数线的
考生,并将他们的成绩按降序打印。
Input
测试输入包含若干场考试的信息。每场考试信息的第1行给出考生人数N ( 0 < N
< 1000 )、考题数M ( 0 < M < = 10 )、分数线(正整数)G;第2行排序给出第1题至第M题的正整数分值;以下N行,每行给出一
名考生的准考证号(长度不超过20的字符串)、该生解决的题目总数m、以及这m道题的题号
(题目号由1到M)。
当读入的考生人数为0时,输入结束,该场考试不予处理。
Output
对每场考试,首先在第1行输出不低于分数线的考生人数n,随后n行按分数从高
到低输出上线考生的考号与分数,其间用1空格分隔。若有多名考生分数相同,则按他们考
号的升序输出。
Sample Input
4 5 25 10 10 12 13 15 CS004 3 5 1 3 CS003 5 2 4 1 3 5 CS002 2 1 2 CS001 3 2 3 5 1 2 40 10 30 CS001 1 2 2 3 20 10 10 10 CS000000000000000001 0 CS000000000000000002 2 1 2 0
Sample Output
3 CS003 60 CS001 37 CS004 37 0 1 CS000000000000000002 20
这是一道排序题,不仅要对分数进行排序,还涉及到字符串的排序(其实都是一个不等号的事情)。所以这里用结构体数组来存储信息,以便自定义cmp函数用sort一遍排序就到位。
没什么好说的,直接贴代码吧:
1 #include<iostream> 2 using namespace std; 3 #include<cstdio> 4 #include<algorithm> 5 #include<iostream> 6 #include<string> 7 struct node 8 { 9 string id;//学号 10 int n;//做对的题数 11 int m[11];//做对的题的题号 12 int sum;//获得的学分 13 }q[1001]; 14 bool cmp(node x,node y)//自定义cmp函数用于sort 15 { 16 if(x.sum!=y.sum)return x.sum>y.sum;//先排分数,再排学号 17 else return x.id<y.id;//字符串是可以直接比较的(我之前不知道),我记得原理根据ascii码按字符逐个比较 18 } 19 int main() 20 { 21 int N; 22 while(cin>>N&&N!=0) 23 { 24 int M,t,b[11]={0}; 25 cin>>M>>t; 26 int num=0; 27 for(int i=1;i<=M;i++)cin>>b[i]; 28 for(int i=1;i<=N;i++) 29 { 30 q[i].sum=0; 31 cin>>q[i].id; 32 cin>>q[i].n; 33 for(int j=1;j<=q[i].n;j++) 34 { 35 cin>>q[i].m[j]; 36 q[i].sum+=b[q[i].m[j]];//计算总分数 37 } 38 if(q[i].sum>=t)num++; 39 } 40 sort(q+1,q+N+1,cmp);//结构体数组下标是1开始的,然后排序到数组最后一个元素的后一位 41 cout<<num<<endl; 42 for(int i=1;i<=num;i++) 43 { 44 cout<<q[i].id<<" "<<q[i].sum<<endl; 45 } 46 } 47 }
来源:https://www.cnblogs.com/hialin545/p/12208259.html