[SinGuLaRiTy] 2017-07-21 综合性测试
【SinGuLaRiTy-1028】 Copyright (c) SinGuLaRiTy 2017. All Rights Reserved.
对于所有题目:Time Limit: 1s | Memory Limit: 256MB
[USACO2016 Jan] 愤怒的奶牛 (Angry Cows)
题目描述
在数轴x上摆放有n(2<=n<=50000)捆干草堆,没有任何两堆在同样的位置,所有的位置均为整数。你可以用弹弓射击射击数轴上的任意地点。如果你用弹弓以R的力度射击x处,那么该处会发生爆炸,爆炸的范围是以R为半径的圆形区域,所以它会使得[x-R,x+R]的所有干草堆同时发生爆炸。这些干草堆的爆炸半径是R-1。它们又会触发连锁反应,第三轮的爆炸的半径为R-2,依次递减。请选择最小的力度射击,使得所有的干草堆全部爆炸。
输入
第一行包含N。接下来N个整数,表示干草堆的位置。所有位置在[0,1000000000]内。
输出
输出最小的力度R,使得所有的干草堆发生爆炸。四舍五入保留一位小数。
样例数据
样例输入 | 样例输出 |
5 |
3.0 |
<样例解释>
如果以力度3射击坐标5,则坐标3,坐标8处的干草堆会发生爆炸,然后又会引爆坐标1和坐标10的干草堆,最后引爆坐标11处的干草堆。
解析
f[i]表示要炸掉i之前的所有干草堆,在i点最少需要的半径。
g[i]表示要炸掉i之后的所有干草堆,在i点最少需要的半径。
找到满足(j<i&&a[i]-a[j]>f[j]+1)的最后一个j,f[i]=min(a[i]-a[j],f[j+1]+1),g数组同理。
最后枚举起始的射击区间,计算答案。
由于答案的小数部分只可能是0或者0.5,所以将所有数乘以2就可以避免小数问题,最后答案再除以2就可以了。
Code
#include<iostream> #include<cstdio> #include<cstdlib> #include<cmath> #include<cstring> #include<algorithm> #define ll long long #define N 50005 #define inf 2000000000 using namespace std; int n,a[N],f[N],g[N]; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); a[i]*=2; } sort(a+1,a+n+1); for(int i=1;i<=n;i++) f[i]=g[i]=inf; int t=1; f[1]=0; for(int i=2;i<=n;i++) { while(t+1<i&&a[i]-a[t+1]>f[t+1]+2) t++; f[i]=min(a[i]-a[t],f[t+1]+2); } t=n; g[n]=0; for(int i=n-1;i>=1;i--) { while(t-1>i&&a[t-1]-a[i]>g[t-1]+2) t--; g[i]=min(a[t]-a[i],g[t-1]+2); } int ans=inf; for(int i=1,j=n;i<j;) { ans=min(ans,max((a[j]-a[i])/2,max(f[i],g[j])+2)); if(f[i+1]<g[j-1]) i++; else j--; } printf("%.1lf",(double)ans/2); return 0; }
[USACO2016 Jan] 无线电通信 (Radio Contact)
题目描述
农夫约翰和奶牛贝西要去寻找丢失的奶牛,为了彼此能联系对方,他们带着无线电通讯设备。不幸的是电池快没有电了。所以它们要尽量节省电能。农夫从位置(fx,fy)出发,一共走N步,贝西从位置(bx,by)出发,一共走M步。农夫的路线是由一个长度为N的字符串限制,字符串只出现’E’或’S’或’W’或’N’中,表示东南西北四个方向。农夫每一单位时间可以选择不动,或者按照限制走出一步。奶牛贝西也是如此。现在无线电设备每一单位时间消耗的电量等于他们的距离的平方。请问,他们走到终点,最少消耗多少电量?
输入
第一行两个整数n,m(1<=n,m<=1000),第二行为fx,fy表示农夫的起始位置,第三行为bx,by,表示贝西的起始位置。
接下来有两个字符串,第一个字符串表示农夫的路线,第二个字符串表示贝西的路线。
他们的坐标总是在(0<=x,y<=1000)。北是y的正方向,东为x的正方向。
输出
输出一个整数,表示最少消耗的电量。
样例数据
样例输入 | 样例输出 |
2 7 |
28 |
解析
其实,这是一道水题。
设f[i][j]表示当前第一个人走了i步第二个人走了j步,那么就有f[i+1][j]=min(f[i+1][j],f[i][j]+dis())。然后f[i][j+1]和f[i+1][j+1]用同样的方法推出来。
Code
#include<cstdio> #include<iostream> #include<cstring> #define N 1024 typedef long long ll; using namespace std; int n,m,a[N][2],b[N][2]; ll f[N][N]; char s[N]; inline int dis(int a,int b,int c,int d) { return (c-a)*(c-a)+(b-d)*(b-d); } int main() { scanf("%d%d",&n,&m); cin>>a[1][0]>>a[1][1]>>b[1][0]>>b[1][1]; scanf("%s",s+1); for(int i=1;i<=n;i++) { if(s[i]=='N') a[i+1][0]=a[i][0],a[i+1][1]=a[i][1]+1; else if(s[i]=='S') a[i+1][0]=a[i][0],a[i+1][1]=a[i][1]-1; else if(s[i]=='W') a[i+1][0]=a[i][0]-1,a[i+1][1]=a[i][1]; else if(s[i]=='E') a[i+1][0]=a[i][0]+1,a[i+1][1]=a[i][1]; } scanf("%s",s+1); for(int i=1;i<=m;i++) { if(s[i]=='N') b[i+1][0]=b[i][0],b[i+1][1]=b[i][1]+1; else if(s[i]=='S') b[i+1][0]=b[i][0],b[i+1][1]=b[i][1]-1; else if(s[i]=='W') b[i+1][0]=b[i][0]-1,b[i+1][1]=b[i][1]; else if(s[i]=='E') b[i+1][0]=b[i][0]+1,b[i+1][1]=b[i][1]; } for(int i=1;i<=n+1;i++) for(int j=1;j<=m+1;j++) f[i][j]=2147483647; f[1][1]=0; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { f[i+1][j]=min(f[i+1][j],f[i][j]+dis(a[i+1][0],a[i+1][1],b[j][0],b[j][1])); f[i][j+1]=min(f[i][j+1],f[i][j]+dis(a[i][0],a[i][1],b[j+1][0],b[j+1][1])); f[i+1][j+1]=min(f[i+1][j+1],f[i][j]+dis(a[i+1][0],a[i+1][1],b[j+1][0],b[j+1][1])); } printf("%I64d",f[n+1][m+1]); return 0; }
[USACO2016 Jan] 熄灯 (Lights Out)
题目描述
谷仓是一个简单的多边形。它的每一条边或平行于x轴,或平行于y轴。从任意一个点沿顺时针方向走,横边和纵边是交替出现的。多边形一共有n个点,编号为1到n。其中1号点为出口。奶牛贝西已经完全记住了谷仓的地图。它从某一个点出发,只能沿着边界走,在开灯的情况,它能很快知道怎么走才能更快到达出口。可是有一天,灯熄灭了。它一下慌了神,然后它忘了自己在哪个点了。但是幸运的是它仍然记得整个谷仓的地图,而且它能够凭触觉知道当前点的内角有多大,它也能感觉到当前点是不是出口,而且经过一条边后也能精确地计算出该边的长度。现在,为了寻找出口,它决定采取这样的策略:沿着顺时针方向继续走下去,直到它能够判断出自己的位置,然后再选择距离最短的方向(顺时针或逆时针)走到出口。求最坏的情况下,贝西走到出口要比灯没坏的情况下多花多少时间。
输入
第一行包含N(4<=N<=200),接下来N行每行包含两个整数,表示多边形的n个顶点(xi,yi),按顺时针的方向给出。这些点的范围均在[-100000,100000]
输出
输出贝西按照上述策略在最坏情况下比正常情况下要多花的时间。
样例数据
样例输入 | 样例输出 |
4 |
2 |
<样例解释>
贝西如果在出口处(Point 1),肯定可以感知到,这绝对不会是最坏情况。现在考虑它在其他3个点时的情形:它当前可以感知到内角的大小。但因为内角都是90度,所以他无法确定自己位置。于是它按照既定的策略,顺时针走:
1.如果它在开始在点2,它需要走到点3,此时,它知道自己在哪里了。于是它找最短的路径,不管哪边,都是11.所以,它一共需要走12个单位。如果是开灯的情况下,只需要走10个单位。所以,它要多走两个单位。
2.如果它在点3,它要走11个单位。开灯的情况下也要走11个单位。
3.如果它在点4,它要走1个单位。开灯的情况下也要走1个单位。
于是最坏的情况下,它要多走两个单位。
解析
这道题,其实最重要的就是贝西判定当前的位置的方法:通过走过一段独一无二的路线。那么,我们怎么用代码进行实现呢?在这里,我用的是vector套pair,这样写也显得有些混乱;有一种用hash来判断的方法,相比之下还是好理解一点。(hash是一个很重要的东西,以后的字符串处理常常会用到) 大体思路就是通过用角度的大小(90度和270度)和边的长度来表达区分不同的路线,如果这个特征值一样,我们就认为贝西无法区分,也就找不到自己的位置;如果特征值不同,贝西就可以辨别出方向了。需要注意的是:数据的处理以及表达方式一定要确保能描述每一条路线的特征,而不会出现不同的路线有相同特征值的情况。
Code
#include<iostream> #include<vector> #include<set> #include<algorithm> #include<cstdio> #define MAXN 210 using namespace std; int opt[MAXN]; int main() { int N; scanf("%d",&N); vector<pair<long long,long long> > A(N); for(int i=0;i<N;i++) { cin>>A[i].first>>A[i].second; } vector<int> S(1,0); for(int i=0;i<N;i++) { int j=(i+1)%N; int k=(i+2)%N; S.push_back(abs(A[i].first-A[j].first)+abs(A[i].second-A[j].second)); if((A[i].first-A[j].first)*(A[k].second-A[j].second)-(A[k].first-A[j].first)*(A[i].second-A[j].second)>0) S.push_back(-1); else S.push_back(-2); } S.back()=0; for(int i=0;i<N;i++) { opt[i+1]=opt[i]+S[i*2+1]; } opt[N]=0; for(int i=N-1;i>=0;i--) { opt[i]=min(opt[i],opt[i+1]+S[i*2+1]); } multiset<vector<int> > st; for(unsigned int i=0;i<S.size();i+=2) { for(unsigned int ln=1;i+ln<=S.size();ln+=2) { st.insert(vector<int>(S.begin()+i,S.begin()+i+ln)); } } int result=0; for(unsigned int i=2;i+2<S.size();i+=2) { int ln; int cost=0; for(ln=1;;ln+=2) { if(st.count(vector<int>(S.begin()+i,S.begin()+i+ln))==1) break; cost+=S[i+ln]; } result=max(result,cost+opt[(i+ln)/2]-opt[i/2]); } cout<<result; return 0; }
[Vijos 1157] 分梨子
题目描述
Finley家的院子里有棵梨树,最近收获了许多梨子。于是,Finley决定挑出一些梨子,分给幼稚园的宝宝们。可是梨子大小味道都不太一样,一定要尽量挑选那些差不多的梨子分给孩子们,那些分到小梨子的宝宝才不会哭闹。每个梨子都具有两个属性值,Ai和Bi,本别表示梨子的大小和甜度情况。假设在选出的梨子中,两个属性的最小值分别是A0和B0。只要对于所有被选出的梨子i,都满足C1*(Ai-A0)+C2*(Bi-B0)≤C3(其中,C1、C2和C3都是已知的常数),就可以认为这些梨子是相差不多的,可以用来分给小朋友们。那么,作为幼稚园园长的你,能算出最多可以挑选出多少个梨子吗?
输入
第一行一个整数N(1≤N≤2000),表示梨子的总个数。 第二行三个正整数,依次为C1,C2和C3(C1,C2≤2000,C3≤10^9)。 接下来的N行,每行两个整数。第i行的两个整数依次为Ai和Bi。
输出
只有一个整数,表示最多可以选出的梨子个数。
样例数据
样例输入 | 样例输出 |
3 |
2 |
解析
神奇的题~
对于这道题,我们要采用"数形结合"的思想:每一个梨子不是有两个特征值Ai和Bi吗,我们要把它们看做一组坐标(Ai,Bi),这样一来,每个梨子就变为了坐标轴上的一个点。我们来继续处理给出的题目信息:在选出的梨子里,有两个最小值,也就是说,对于所有我们选中的梨子,都满足:Ai>A0,Bi>B0,也就是说,所有的我们选中的梨子所代表的的点,都应该位于直线x=A0的右边,直线y=B0的上方。<是不是很"神奇"?>我们继续,题目又说了:每一个梨子(点)都满足C1*(Ai-A0)+C2*(Bi-B0)≤C3,我们用更"数学"的语言翻译一下,就变成:“每一个点(X,Y)都满足M*(X-a)+N(Y-b)≤C (M,N,C均为常数,我们在这里也暂且把a和b当做常数)”,嘿,这不是高中数学教材里的线性规划不等式吗?下面我们来画一画图(图中的点代表梨子):
如图,三条直线围成的这个直角三角形内的点就是我们能够选择的梨子了。欢呼了?雀跃啦?不行,现在直接枚举A0、B0,就已经是O(n^2)了,然后再在三角形里面扫描满足条件的梨子,O(n^3)直接超时。于是乎,我们要想一个法子,让扫描梨子这一项的工程量减小一点。我们先将原来的不等式变为:C1Ai+C2Bi-C3<=C1A0+C2B0 。然后我们可以这么搞:先在外层来一个for循环确定A0,接下来我们再由低到高枚举B0,这时看一看我们刚刚得出的不等式,发现等式右边的值变大了,也就是说会有新的梨子满足这个不等式。——哎,有法子了!我们在枚举A0、B0之前预处理一下:1>记录每一个y轴(y=1,y=2,y=3......)上的梨子(点)的数量,2>将梨子(点)按C1Ai+C2Bi-C3排序。我们在枚举B0的过程中,先删去由于B0增大而从三角形下方剔除出去的梨子(由于有了<1>预处理,这个过程就是O(1)的了),再加上新的满足不等式的值(<2>预处理也大大见减少了时间),不断更新,然后取最大值,最后就能得到答案了。
Code
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring>
using namespace std; const int MAXN=2005;
struct node { int a,b,s; }p[MAXN];
int c1,c2,c3; int ans; int n; inline bool cmp(const node&a,const node&b) { return a.s<b.s; } void init() { scanf("%d",&n); scanf("%d%d%d",&c1,&c2,&c3); for(int i=1;i<=n;i++) { scanf("%d%d",&p[i].a,&p[i].b); p[i].s=p[i].a*c1+p[i].b*c2; } } int main() { init(); sort(p+1,p+n+1,cmp); for(int i=1;i<=n;i++) { int a0=p[i].a; int b0=p[i].b; int cur=i; int tot=0; while(p[cur++].s<=c1*a0+c2*b0+c3&&cur<=n) tot++; ans=max(ans,tot); } cout<<ans<<endl; return 0; }
Time: 2017-07-21