T1:问题 A: 数矩形
题目描述
最近某歌手在研究自己的全球巡回演出计划,他将所有心仪的城市都用平面上的一个点来表示,并打算从中挑选出 4 个城市作为这次巡回演出的地点。
为了显示自己与众不同,他要求存在一个矩形使得挑选出的 4 个点恰好是这个矩形的 4 个顶点,并且希望这个矩形的面积最大。
这可急坏了其经纪人,于是他向全球歌迷征集方案,当然你这位歌迷一定不会错过这个机会。
输入
从文件input.txt中读入数据,输入文件的第一行是一个正整数N,表示平面上点的个数(即某歌手心仪的城市数)。
接下来的N行,每行是由空格隔开的两个整数Xi和Yi,表示其对应点的坐标。
20%的数据满足N≤500,
100%的数据满足N≤1500,−10^8≤Xi,Yi≤10^8,且输入数据保证答案存在。
输出
输出文件 output.txt 仅包含一个非负整数,表示最大的矩形面积。
样例输入
8 -2 3 -2 -1 0 3 0 -1 1 -1 2 1 -3 1 -2 1
样例输出
10
题解
如何确定一个矩形?先确定矩形的中点,再确定对角线的长度,最后确定对角线的角度,就可以确定一个矩形。如何判断一个图形是一个矩形呢?根据前两个条件就可以了。因此可以预处理出所有对角线的信息:中点坐标,长度,以及向量的x,y。然后中点相同并且长度相同的一些线段就可以两两之间构成矩形,累加一次答案。故按照上述排序即可(关键字顺序无妨),然后判断连续的一些能构成矩形的线段,每得到一次矩形就用叉积求矩形面积。可以证明,格点上矩形的面积一定为整数,因此用叉积计算不会掉精度。注意用重载运算符时可能要定义函数。
参考代码
#include<cstdio>
#include<algorithm>
#define LL long long
using namespace std;
struct node
{
LL x,y;
node(int xi = 0, int yi = 0):x(xi), y(yi){}//构造函数
}a[1510];
LL tot=0,n,ans=0;
LL abs1(LL p) { return p>0?p:-p; }
LL max1(LL p,LL q) { return p>q?p:q; }
node operator-(node p,node q)
{
return node(p.x-q.x,p.y-q.y);
}
node operator+(node p,node q)
{
return node(q.x+p.x,q.y+p.y);
}
bool operator<(node p,node q)
{
return (p.x<q.x)||(p.x==q.x&&p.y<q.y);
}
bool operator==(node p,node q)
{
return (p.x==q.x)&&(p.y==q.y);
}
LL multiple1(node p,node q)
{
return abs1(p.x*q.y-q.x*p.y);
}
LL figure(node p,node q)
{
return (p.x-q.x)*(p.x-q.x)+(p.y-q.y)*(p.y-q.y);
}
struct node2
{
node mid,v;
LL dis;
}b[2260000];
bool comp1(node2 p,node2 q)
{
return (p.dis<q.dis)||(p.dis==q.dis&&p.mid<q.mid);
}
int main()
{
scanf("%lld",&n);
for(LL i=1;i<=n;i++)
{
scanf("%lld%lld",&a[i].x,&a[i].y);
}
for(LL i=1;i<n;i++)
{
for(LL j=i+1;j<=n;j++)
{
b[++tot].mid=a[i]+a[j];
b[tot].dis=figure(a[i],a[j]);
b[tot].v=a[i]-a[j];
}
}
sort(b+1,b+tot+1,comp1);
for(LL i=1,j=1;i<=tot;i=j)
{
while(b[j].mid==b[i].mid&&b[j].dis==b[i].dis) j++;
for(LL k=i;k<j;k++)
for(LL p=k+1;p<j;p++)
{
ans=max1(ans,multiple1(b[k].v,b[p].v)/2ll);
}
}
printf("%lld",ans);
return 0;
}
T2:问题 B: 任务调度
题目描述
有 n 个任务和两台机器 A 与 B。每个任务都需要既在机器 A 上执行,又在机器 B 上执行,
第 i 个任务需要在机器 A 上执行时间 ai,且需要在机器 B 上执行时间 bi。最终的目标是所有任务在 A 和 B 上都执行完,且希望执行完所有任务的总时间尽量少。当然问题没有这么简单,有些任务对于先在机器 A 上执行还是先在机器 B 上执行有一定的限制。据此可将所有任务分为三类:
1. 任务必须先在机器 A 上执行完然后再在机器 B 上执行。
2. 任务必须先在机器 B 上执行完然后再在机器 A 上执行。
3. 任务没有限制,既可先在机器 A 上执行,也可先在机器 B 上执行。
现在给定每个任务的类别和需要在机器 A 和机器 B 上分别执行的时间,问使所有任务都能按规定完成所需要的最少总时间是多少。
输入
输入的第一行只有一个正整数 n,表示任务的个数。
接下来的 n行,每行是用空格隔开的三个正整数 ti,ai,bi,分别表示第 i个任务的类别(类别1,2,3的定义如上)以及第 i个任务需要在机器 A 和机器 B 上分别执行的时间。
输出
输出仅包含一个正整数,表示所有任务都执行完所需要的最少总时间。
样例输入
<span style="color:#333333"><span style="background-color:#f5f5f5">3
3 5 7
1 6 1
2 2 6</span></span>
样例输出
<span style="color:#333333"><span style="background-color:#f5f5f5">14</span></span>
提示
样例 1 解释
一种最优任务调度方案为:
机器 A 上执行的各任务依次安排如下:
任务 1 (0→5),任务 2 (5→11), 任务 3 (11→13);
机器 B 上执行的各任务依次安排如下:
任务 3 (0→6), 任务 1 (6→13), 任务 2 (13→14),
这样,所有任务都执行完所需要的总时间为14。
数据规模与约定
对于 100%的数据,保证 1≤n≤20,1≤ai≤10^3,1≤ti≤3,并保证 ti=3的i不超过10个
题解
这道题其实很恐怖的。输出A总和与B总和中的最大值可以得60分,输出最大值+1可以得30分。由于这道题要用到随机算法,因此先把大体思路讲讲。讲之前,要注意,排序只是为了使结果较优,没有办法一定正确,但是加入随机化能基本上保证答案不错。如果已经知道A的顺序和B的顺序,那么完全可以分开求A的最短时间,B的最短时间,然后取一个max即可。贪心思路:如果求A的最短时间,只需要先把第一类的A累加,再枚举第二类的B(因为第二类要先B),再看A应该加到哪个的总时间后面,因为有可能B的总时间反超了A,此时也就只能等着,所以是取max。排序的方式也是为了减少这种情况,让前面的尽量满。对于第一类,先按照B从大到小,再在B相同的情况下,按照A从小到大排序。第二类也类似。如何处理第三类?由于不超过10个,因此直接搜索就可以了,把第三类加入到第一类和第二类中。
参考代码
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define MAXN 50
using namespace std;
struct node
{
int ta,tb;
}A[MAXN],AA[MAXN],B[MAXN],BB[MAXN],C[MAXN];
int maxn=99999999,n;
int min1(int p,int q) { return p<q?p:q; }
int max1(int p,int q) { return p>q?p:q; }
int lena,lenb,lenc;
bool comp_a(node p,node q)
{ return p.tb==q.tb?p.ta<q.ta:p.tb>q.tb; }
bool comp_b(node p,node q)
{ return p.ta==q.ta?p.tb<q.tb:p.ta>q.ta; }
int swap1(node & p,node & q)
{ node h=p;p=q;q=h; }
int calc()
{
int num=0,time_a=0,time_b=0;
for(int i=1;i<=lena;i++)
time_a+=AA[i].ta;
for(int i=1;i<=lenb;i++)
{
time_b+=BB[i].tb;
if(time_a>time_b) time_a+=BB[i].ta;
else time_a=time_b+BB[i].ta;
}
num=time_a;
time_a=time_b=0;
for(int i=1;i<=lenb;i++)
time_b+=BB[i].tb;
for(int i=1;i<=lena;i++)
{
time_a+=AA[i].ta;
if(time_a>time_b) time_b=time_a+AA[i].tb;
else time_b+=AA[i].tb;
}
num=max1(num,time_b);
return num;
}
int figure()
{
for(int i=1;i<=lena;i++) AA[i]=A[i];
for(int i=1;i<=lenb;i++) BB[i]=B[i];
sort(AA+1,AA+lena+1,comp_a);
sort(BB+1,BB+lenb+1,comp_b);
int res=calc(),X1,X2,Y1,Y2,ans;
for(int i=1;i<=1000;i++)
{
if(lena) X1=rand()%lena+1,X2=rand()%lena+1;
if(lenb) Y1=rand()%lenb+1,Y2=rand()%lenb+1;
if(lena) swap1(AA[X1],AA[X2]);
if(lenb) swap1(BB[Y1],BB[Y2]);
ans=calc();
if(ans<res) res=ans;
else
{
if(lena) swap1(AA[X1],AA[X2]);
if(lenb) swap1(BB[Y1],BB[Y2]);
}
}
return res;
}
void dfs(int k)
{
if(k==lenc+1)
{
maxn=min1(maxn,figure());
return;
}
A[++lena]=C[k];
dfs(k+1);
--lena;
B[++lenb]=C[k];
dfs(k+1);
--lenb;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int t;scanf("%d",&t);
if(t==1)
{
++lena;
scanf("%d%d",&A[lena].ta,&A[lena].tb);
}
else if(t==2)
{
++lenb;
scanf("%d%d",&B[lenb].ta,&B[lenb].tb);
}
else if(t==3)
{
++lenc;
scanf("%d%d",&C[lenc].ta,&C[lenc].tb);
}
}
dfs(1);
printf("%d",maxn);
return 0;
}
T3:问题 C: 赛车游戏
题目描述
名歌手LAALA最近迷上了一款赛车游戏,游戏中开车的玩家在不同的路段需要选择不同的速度,使得自己在最短的时间内到达终点。开始游戏时,车内的初始油量为f,所以游戏的关键是如何在速度和耗油量之间实现平衡。
LAALA 经过一段时间的研究后,发现这款游戏可以用一个简单的数学模型来描述,具体来说:从起点到终点的路线可以被简化成折线段,每条线段代表一个上坡或者下坡,若在一段斜率为 s(s>0 代表上坡,s=0 代表平地,s<0 代表下坡)的道路上以速度 v km/h 行驶,则每公里的耗油量为 max(0,av+bs),其中 a 和 b 为游戏的内置参数,分别表示在平地行驶时的耗油率及斜坡对耗油量的影响(b 恒为正)。
这里假设,加速和减速不耗油,且看成是瞬间完成的,所以即使在同一条线段上也可采取以不同的速度行驶的策略来缩短耗费的时间。
由于 LAALA 在以前的游戏中表现不佳,现在使用的车型依然是系统初始分配的,所以它的速度不能超过 vmax km/h。在获得这些参数后,LAALA 想知道在初始油量受限的情况下(中途不许加油)自己能得到的最佳成绩是多少。作为 LAALA 的歌迷,你能帮帮他吗?
输入
从文件input.txt中读入数据,输入文件的第一行是一个正整数T,表示数据组数。对每组数据,第一行是用空格隔开的4个浮点数a、b、vmax和f,其中a、b和vmax的意义如前所述,f表示初始油量,其单位也与前面的描述保持一致。第二行是一个正整数r,表示线段的数目。接下来的r行,每行是用空格隔开的2个浮点数xi和yi,分别表示在标准笛卡耳坐标系下该线段在水平方向和垂直方向的改变量(单位为米)。
输出
输出文件 output.txt 包含 T 行,依次对应输入中的 T 组数据。对某组数据,若基于初始油量无法到达终点,则对应行输出 IMPOSSIBLE,否则输出最少需要的时间(单位为小时)。
样例输入
3 10.0 1.0 150 0.0 1 100.0 -100.0 10.0 100.0 150 1.0 2 100 0 100 100 0.5 0.1 100 10 3 1000 0 100 10 100 -10
样例输出
1.41421 IMPOSSIBLE 0.07212
提示
【数据范围】
100%的数据满足
T≤100, 0.1≤a≤100, 0.1≤b≤100,
10≤vmax≤200, 0≤f≤50,r≤10000,
1≤xi≤1000, -1000≤yi≤1000,且如果问题有解,那么答案不超过 24。
你所输出的答案需要恰好保留到小数点后 5 位,当且仅当你的输出与标准答案完全一致时你的输出才被视作正确。
题解
这道题得上坡下坡分开看。下坡的话,由于小于0不耗油,就可以直接上临界状态,得到速度为
-bs/a,然后与vmax取一个min,就可以达到下坡的最优。关键在于上坡怎么走??有些题解上说了,根据多次测试,v不变的情况下时间最短,却并未证明。有一个很简单的方法可以解。
由于a,b恒为正(注意直接看数据范围),当s为正时,av+bs一定为正。已知bs的总和与速度无关,是一个常量,因此可以不管。又因为a也是常量,可以直接消去。因此读题后的总耗油量可以粗略的表示成一个函数形式:。由于显然油量耗尽时,能得到最短时间,因此也可以认为该式子的右边是一个定值。而题目中要求的是求
,也就是时间的最小值。不妨设
。我们猜想v相同时,时间最短,因此表现形式为
。此刻带入
就可以得到猜想的最短时间,故需要证明
恒成立,当且仅当v相等时取等。这是不是与求不等式的临界很像?
把中的v全部换成
,把整个式子写出来,就是要证明如下不等式:
将不等式左边的分母乘到右边(注意这个分母一定大于0),就变成了(写开之后):
很容易发现,这就是一个标准的柯西不等式(广义),取等的条件就是v相等。
因此直接二分速度,然后判断是否能在规定油量内跑完即可。防止出现死循环,就用num来计数
参考代码
#include<cstdio>
#include<cmath>
#define EXP 1e-15
using namespace std;
int t,n,num;
double a,b,vmax,f,X[100001],Y[100001],len[100001],K[100001],l,r;
double max_d(double p,double q)
{ return p-q>EXP?p:q; }
double min_d(double p,double q)
{ return p-q<EXP?p:q; }
bool check(double v)
{
double ret=0.0;
for(int i=1;i<=n;i++)
{
ret+=max_d(0.0,a*v+b*K[i])*len[i];
if(ret+EXP>=f) return 0;
}
return 1;
}
double calc(double v)
{
double ret=0.0;
for(int i=1;i<=n;i++)
{
double temp=a*v+b*K[i];
if(temp<=EXP)
{
double vv=min_d(vmax,-b*K[i]/a);
ret+=(double)len[i]/vv;
}
else
{
if(v<=EXP) return 0;
ret+=(double)len[i]/v;
}
}
return ret;
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%lf%lf%lf%lf",&a,&b,&vmax,&f);
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%lf%lf",&X[i],&Y[i]);
X[i]/=1000.0;Y[i]/=1000.0;
K[i]=Y[i]/X[i];
len[i]=sqrt(X[i]*X[i]+Y[i]*Y[i]);
}
l=num=0,r=vmax;
while(num<2000)
{
num++;
double mid=(l+r)/2.0;
if(check(mid)) l=mid;
else r=mid;
}
l=calc(l);
if(l<=EXP) printf("IMPOSSIBLE\n");
else printf("%.5lf\n",l);
}
return 0;
}