更新--学习笔记 凸包 && K短路

这周CYC的套题里出现了两个新的算法,一个是计算几何之凸包,还有一个是最短路扩展之K短路,在此做一个浅析

凸包专题:
首先要知道一个什么样的东西叫做凸包:
凸包呢(用百科的话来说):用不严谨的话来讲,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边型,它能包含点集中所有的点。
类似这样的图形:(没错又是百度百科)

那么构建凸包之后我们相当于用最短的长度将平面内所有的点包围起来(两点之间线段最短),对于一个凸包,我们从它的原点(即图中红点),向点集中的中间点(图中点6)连一根线,会将凸包分成两个凸状的东西。。。向上凸的叫上凸壳,向下的就叫下凸壳。

那么如何构建一个凸包呢,我们需要了解到一个东西:向量的叉积,用来判断平面内两向量之间的极角(也就是描述类似下图中向量OB,在向量OC右边)

那么我们就可以先将平面上点的坐标按X坐标排序,之后呢分上凸壳,下凸壳来构建,因为分开来,图形具有上凸或下凸的性质,我们就可以开一个栈,不断调整一个三元点对(i,j,k)看哪一对点可能组成凸包的一个线段,举个例子:线段下凸,即如果有不合法的点,那么新进来一个点,我们和当前栈顶前一个点连线,用叉积判断是否在当前栈顶和前一个点的连线的右边,如果是,则当前栈顶不可能在凸包上(因为图形下凸),以此类推,来更新图形的线段,由于点的坐标呈有序性,则必定能够得到凸包。。。。(在图形上按照这种方法推演一下构建凸包有助于理解)

具体的代码不贴了,凸包的运用会在下一篇<<斜率优化>>中概述。。这种算法复杂度为
O(nlogn) ,性能非常优秀。。

再来讲讲K短路:

K短路,就是在图形中找到第K短的路径,求最短路径中一我们会运用SPFA或者是Dij + heap求解,运行很快,那么对于这一类问题,我们的想法一般是对于一张图,先寻找第一短路,然后寻找第二短路,直至寻找到第K短路。。那么在求解最短路的情况下就可以运用SPFA和Dij + heap了(前提是路径不重复的情况下)

先不考虑路径重不重复,先想一般求解方法,也就是这个不断寻找第i短路的过程怎么实现呢??我们一般会运用BFS来求解,不断扩展路径长度的状态,然后对于每个点记录路径长度,最后取出第K小,很容易发现,我们扩展出了非常多无用的状态,而且这种方法很可能会更新出第K+1短路,明显是不需要的,这时候我们就需要一个新算法:A*算法。

A*算法呢,没学过的请先自行移步补脑,我们可以减去非常多无用的状态,而且因为估价函数的存在和可更新性,我们就可以实现从最短路不断找到K短路的过程了,首先我们将整个图反向建边,用SPFA求出每个点i,到点n的最短路(为了保证估价函数的正确性),然后呢我们设A*算法中的估价函数为:f(j) = g(i)[已经走过路径长度] + dis(j)[点j到n的最短路径] + len(i,j)[这条边的长度]; 因为我们每次都需要取一个最小值来更新,可以使用优先队列等等来维护估价函数,而且为了使路径不重复,我们每一次更新完都需要pop掉最小值来更新第i+1短路。

对于A*中估价函数的求值我们就是搭配上BFS来解题了,算法复杂度比较玄学啊。
O(klogn) O(nk) 之间吧。

代码:

# include<cstdio>
# include<queue>
# include<vector>
# include<cstring>
using namespace std;
const int N = 2e5 + 10;
struct node1
{
    int to,nex,val;
}e[N],e1[N];
struct node
{
    int f,g,x;
    bool operator<(const node &a)const
    {
        if (a.f == f)
            return a.g < g;
        return a.f < f;
    }
};
int ins[N],st[N],st1[N],dis[N];
int n,m,k,i,tot,ans;
void add(int u,int v,int len)
{
    e[++tot].to = u;
    e[tot].nex = st[v];
    e[tot].val = len;
    st[v] = tot;
    e1[tot].to = v;
    e1[tot].nex = st1[u];
    e1[tot].val = len;
    st1[u] = tot;
}
void spfa()
{
    memset(dis,0x3f,sizeof(dis));
    queue<int> q;
    dis[n] = 0,ins[n] = 1;
    q.push(n);
    while (!q.empty())
    {
        int rt = q.front();
        for (int i = st[rt];i;i = e[i].nex)
        {
            int v = e[i].to;
            if (dis[v] > dis[rt] + e[i].val)
            {
                dis[v] = dis[rt] + e[i].val;
                if (!ins[v])
                {
                    q.push(v);
                    ins[v]++;
                }
            }
        }
        q.pop();ins[rt]--;
    }
}
int selectk()
{
    node rt,now; int sum = 0,last = 0;
    priority_queue<node> q;
    now.g = 0,now.x = 1,now.f = dis[1];
    q.push(now);
    while (!q.empty())
    {
        rt = q.top();
        q.pop();
        if (rt.x == n && rt.g != last)//rt.g != last 是为了避免出现最短路长度重复的情况
        {
            sum++;
            last = rt.g;
            if (sum == k)
                return last;
        }
        for (int i = st1[rt.x];i;i = e1[i].nex)
        {
            int v = e1[i].to;
            now.g = rt.g + e1[i].val;
            now.f = now.g + dis[v];
            now.x = v;
            q.push(now);
        }
    }
    return 0;
}
int main()
{
    int u,v,len;
    scanf("%d%d%d",&n,&m,&k);
    for (i = 1;i <= m; i++)
    {
        scanf("%d%d%d",&u,&v,&len);
        add(u,v,len);
    }
    spfa();
    ans = selectk();
    printf("%d",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值