图论/最短路径
pku3072:题目大意,给定n个点,求一个机器人从点1到点n的最少用时,机器人每步走过的距离不能超过r,沿着某个方向到达一个点后,如果想到达另一个点,那么需要转过一定的角度朝向那个点才行(可能转过0度)。
解法:最短路径DP,令dis[v][u]表示从起点1经过某些顶点后到达u,再到达v的最短用时,则dis[v][u] = Min{d[v][u], dis[u][j]+map[u][v]+turnDis(p[j], p[u], p[v])}。
方程比较简单,关键是turnDis(p[j],p[u],p[v])怎么算,也就是说转过的角度。我是这么做的,假设p[j]->p[u]形成向量(x1,y1),p[u]->p[v]形成向量(x2,y2),那么向量夹角为atan2(y2,x2)-atan2(y1,x1),不过这样算出的夹角可能大于pi,这个时候取2*pi-(atan2(y2,x2)-atan2(y1,x1))就可以了。
测试数据地址:http://ai.stanford.edu/~chuongdo/acm/2006/
#include <iostream>
#include <cstdio>
#include <cmath>
#include <queue>
#include <cstring>
usingnamespace std;
constint N =25;
constdouble eps = 1e-6;
constdouble pi = acos(-1);
struct node
{
double x, y;
}p[N];
int n;
double r, dis[N][N], map[N][N];
bool visit[N];
queue <int> q;
double Dis(const node &a, const node &b)
{
double x, y;
x = b.x - a.x;
y = b.y - a.y;
return sqrt(x*x+y*y);
}
int dbcmp(double x)
{
if(x > eps) return1;
elseif(x <-eps) return-1;
elsereturn0;
}
double turnDis(node p0, node p1, node p2)
{
double x1, y1, x2, y2, tmp;
x1 = p1.x - p0.x;
y1 = p1.y - p0.y;
x2 = p2.x - p1.x;
y2 = p2.y - p1.y;
//double tmp1 = atan2(y2, x2);
//double tmp2 = atan2(y1, x1);
tmp = fabs(atan2(y2,x2)-atan2(y1,x1));
if(tmp > pi) tmp =2*pi-tmp;
return tmp*180.0/pi;
}
void spfa()
{
int u, i, j;
double tmp;
while(!q.empty()) q.pop();
q.push(1);
memset(visit, false, sizeof(visit));
while(!q.empty())
{
u = q.front();
q.pop();
visit[u] =false;
for(i =1; i <= n; i++)
if(i!=u && dbcmp(map[u][i]-r)<=0)
for(j =0; j <= n; j++)
{
if(j==0&& u!=1) continue;
if(j!=i && dbcmp(dis[u][j]) >=0)
{
tmp = turnDis(p[j],p[u],p[i]);
if(dis[i][u]<0|| dbcmp(dis[i][u]-(dis[u][j]+tmp+map[u][i]))>0)
{
dis[i][u] = dis[u][j]+tmp+map[u][i];
if(!visit[i])
{
q.push(i);
visit[i] =true;
}
}
}
}
}
}
int main()
{
int i, j;
double ans;
while(scanf("%lf%d", &r, &n) != EOF)
{
if(n ==-1) break;
for(i =1; i <= n; i++) scanf("%lf%lf", &p[i].x, &p[i].y);
p[0].x = p[1].x-(p[n].x-p[1].x);
p[0].y = p[1].y-(p[n].y-p[1].y);
for(i =1; i <= n; i++)
for(j =0; j <= n; j++)
{
dis[j][i] =-1.0;
map[i][j] = Dis(p[i], p[j]);
}
dis[1][0] =0;
spfa();
ans =-1.0;
for(j =1; j < n; j++)
if(dbcmp(dis[n][j])>=0&& (ans<0|| dbcmp(ans-dis[n][j])>0))
ans = dis[n][j];
if(ans <0) printf("impossible\n");
else printf("%.0f\n", ans);
}
return0;
}
pku3662:题目大意,给定一个无向加权图,求从顶点1到顶点n的一条路径,其中有k条边的费用可以给你报销,求使不能被报销的边的最大值最小~
解法:这种题,一看题目让求的量基本上知道八成就是二分。这个题确实也是二分~二分那个最小值limit,然后求从顶点1到顶点n最少需要经过多少条边权大于limit的边。
花了20+分钟小水了一下~看会儿电视去~用G++47ms,900+k内存,C++交了一次32ms,400+k内存,竟然还排在rank 2。。。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
usingnamespace std;
constint N =1010;
constint M =20100;
int n, m, k, tot, dis[N], h[N], v[M], nxt[M], w[M];
bool visit[N];
queue <int> q;
void add(int a, int b, int c)
{
v[tot] = b;
w[tot] = c;
nxt[tot] = h[a];
h[a] = tot++;
}
bool spfa(int limit)
{
int u, i, tmp;
while(!q.empty()) q.pop();
memset(visit, false, sizeof(visit));
memset(dis, -1, sizeof(dis));
q.push(1);
dis[1] =0;
while(!q.empty())
{
u = q.front();
q.pop();
visit[u] =false;
for(i = h[u]; i !=-1; i = nxt[i])
{
if(w[i] > limit) tmp =1;
else tmp =0;
if(dis[v[i]]==-1|| dis[v[i]]>dis[u]+tmp)
{
dis[v[i]] = dis[u] + tmp;
if(!visit[v[i]])
{
visit[v[i]] =true;
q.push(v[i]);
}
}
}
if(dis[n]!=-1&& dis[n]<=k) returntrue;
}
returnfalse;
}
int main()
{
int i, a, b, c, l, r, mid, ans;
scanf("%d%d%d", &n, &m, &k);
tot =0;
r =-1;
memset(h, -1, sizeof(h));
for(i =0; i < m; i++)
{
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
add(b, a, c);
r = max(r, c);
}
l =0;
ans =-1;
while(l <= r)
{
mid = (l+r) >>1;
if(spfa(mid))
{
ans = mid;
r = mid-1;
}
else l = mid+1;
}
printf("%d\n", ans);
return0;
}