洛谷 P1027 Car 的旅行(计算几何)
题意
中文题还写个p题意,复制粘贴在这里:
思路
简单粗暴最短路,问题其实在计算几何的部分。每个城市给矩形三个点,然后计算第四个点,只需要先两两组合成向量,看是不是垂直(点积为0),如果是垂直,向量相加就是第四个点和刚才三个点的垂足形成的向量,加上垂足的坐标就是第四个点的坐标了。然后就是两两之间连上边,同一个城市是高铁线路,不同城市间是飞机,最后A城市跑四次最短路,一共求16次最小值即可。
麻烦还是很麻烦的,写了一节算法课加上晚上半小时调bug,可能是计算几何不太熟练。但是这个题真的一点坑都没有,一发CE(OI里默认不用C++11?不懂你们OI圈。。。emplace_back居然是C++11特性,学到了)之后就过了。
代码
#include <iostream>
#include <queue>
#include <vector>
#include <cstring>
#include <cmath>
#include <cfloat>
#include <iomanip>
using namespace std;
struct vect
{
double x;
double y;
vect()
{
x=0;
y=0;
}
void fuck(double x1,double y1,double x2, double y2){
x=x2-x1;
y=y2-y1;
}
double operator * (vect v2){
return x*v2.x+y*v2.y;
}
vect operator + (vect v2){
v2.x+=x;
v2.y+=y;
return v2;
}
double distance(vect v2){
return sqrt((x-v2.x)*(x-v2.x)+(y-v2.y)*(y-v2.y));
}
}nodes[405];
struct edge
{
int to;
double dist;
int nxt;
edge(int t,double d,int nx):to(t),dist(d),nxt(nx){
}
};
int egs[1005];
vector<edge> edges;
struct cmp
{
bool operator() (const pair<double,int> a, const pair<double,int> b)const{
if(a.first!=b.first)
return a.first<b.first;
else
return a.second<b.second;
}
};
void addedge(int f,int t,double w)
{
//cout<<f<<" "<<t<<" "<<w<<endl;
edges.emplace_back(t,w,egs[f]);
egs[f]=edges.size()-1;
}
double dis[1000];
void dijsktra(int s)
{
for(int i=0;i<=405;i++)
{
dis[i]=1000000007;
}
dis[s]=0;
priority_queue<pair<double,int>,vector<pair<double,int> >, cmp > qq;
//堆顶是最小元素,按pair的first排序,所以把dis值放first(当年T到怀疑人生。。)
qq.push(make_pair(dis[s],s));
//源点入堆
while(!qq.empty())
{
double preval=qq.top().first;
int x=qq.top().second;
//cout<<x<<endl;
//和未优化的版本一样找最小元素
qq.pop();
if(dis[x]<preval)
{
//如果目前的值已经比进堆时小了,就没必要再过一遍了,有的题会卡这个时间
continue;
}
for(int j=egs[x];j!=-1;j=edges[j].nxt)
{
if(dis[edges[j].to]>dis[x]+edges[j].dist)
{//一样的松弛操作,但是没有松驰过的点就没必要进堆了
dis[edges[j].to]=dis[x]+edges[j].dist;
qq.push(make_pair(dis[edges[j].to],edges[j].to));
}
}
}
}
int main() {
double s,t;
int a,b;
int n;
cin>>n;
while(n--)
{
edges.clear();
memset(egs,-1,sizeof egs);
cin>>s>>t>>a>>b;
int points=0;
for(int i=0;i<s;i++)
{
double x[4],y[4],tt;
cin>>x[0]>>y[0]>>x[1]>>y[1]>>x[2]>>y[2]>>tt;
vect v1,v2;
double cornerx=0;
double cornery=0;//垂足
for(int j=0;j<3;j++)
{
v1.fuck(x[j],y[j],x[(j+1)%3],y[(j+1)%3]);
v2.fuck(x[j],y[j],x[(j+2)%3],y[(j+2)%3]);
if(v1*v2==0){//两向量垂直
cornerx=x[j];
cornery=y[j];
break;
}
}
vect v3=v1+v2;
x[3]=v3.x+cornerx;
y[3]=v3.y+cornery;//从向量得到第四个点坐标
for(int j=0;j<4;j++)
{
nodes[points].x=x[j];
nodes[points].y=y[j];//把点存起来方便后来算飞机线
points++;
}
int limi=points-4;//本城市的开始点
for(int j=limi;j<points;j++)
{
for(int k=limi;k<points;k++)
{
if(j==k)
{
continue;
}
addedge(j,k,tt*nodes[j].distance(nodes[k]));//高铁线
}
for(int k=0;k<limi;k++)//和之前的点连飞机线
{
addedge(j,k,t*nodes[j].distance(nodes[k]));
addedge(k,j,t*nodes[j].distance(nodes[k]));
}
}
}
a--;
b--;//因为我用0开始的点编号,所以都-1
double ans=DBL_MAX;
for(int i=a*4;i<(a+1)*4;i++)
{
dijsktra(i);//共跑四次最短路
for(int j=b*4;j<(b+1)*4;j++)
{
ans=min(ans,dis[j]);//共求16次最小值
}
}
cout<<fixed<<setprecision(1)<<ans<<endl;//保留一位小数输出
}
return 0;
}
参考了以下文章,在此表示感谢