POJ 2449 - A*初步+ K短路

3 篇文章 0 订阅

    原以为A*会很难懂~~~结果狐狸大大一口气就给我讲懂了A*和K短路的求法.....

    所谓A*就是启发是搜索..说白了就是给BFS搜索一个顺序使得搜索更加合理减少无谓的搜索..如何来确定搜索的顺序?..也就是用一个值来表示这个值为f[x]..每次搜索取f[x]最小的拓展...那么这个f[x]=h[x]+g[x]其中这个h[x]就是当前搜索时的代价..如求K段路这个就是前一个点的h[x']+边的长度...而g[x]是一个估价函数..估价函数要小于是对当前点到目标的代价的估计..这个估计必须小于等于实际值~~否则会出错...A*的关键也就是构造g[x]..

    而这里要说的求K短路一种方法..就是用BFS+A*来搜索的过程...g[x]的设定为到这个点到目标点的最短路径...显然其实小于等于实际值的...h[x]就是搜索到这个点的代价..用一个优先队列来做..每次取出h[x]+g[x]最小的点来拓展...拓展也就是通过这点来更新其能直接经过一条边到达的点..这里做好一个新点就丢进优先队列里去..反正总会从对首弹出h[x]+g[x]最小的点..可以想一下...如果当前取出的优先队列头是一个点e并且是第一次取出h..那么就找到了一条从源点到h的最短路径..这里其实很djikstra的感觉差不多..如果第二次在对头取出了e..则是找到了一条从源点到h的第二短路径..依次类推..第几次从对头弹出e..则找到了从源点到e的第几短路径..

    那要是本身就不存在K短路呢??那就是e拓展不到K但是其他点很有可能一直打圈圈无限下去...这里就要用个条件来判断一下...首先在找某个点作为优先队列头出现了几次就用了一个计数器times[]..所求的点times[e]==k就代表得到了解..如果当前想拓展的点times[]>k就没必要拓展了..因为这个点已经是求到k+1短路了..从这个点继续往下搜肯定得到的是大于等于k+1短路的路径...就像1->2有3条路..2->3有2条路..那1->3有6条路的概念差不多..没必要对其进行拓展了..

    还有一点要特别注意的就是题目要求必须要走..也就是s==e时..k++....

    补充说一下STL的priority_queue 也就是STL就已经有一个优先队列了..像sort一样的可以直接使用..

    声明   #include<queue>

    定义   priority_queue<类型> 变量名 

    但注意的是若是对结构体用..则需要在结构体中对 < 进行重载...如这道题的struct就应该写成这样:

 

struct node
{
     int p,g,h;
     bool operator < (node a) const
     { 
          return a.g+a.h<g+h;
     }     
}; 

      这样用priority_queue就能按需求来进行优先级了...插入时用push..取队首用top..再弹出pop...判空empty...啥啥的..和一般的queue一样...很好使...

 

 


Program:

 

#include<iostream>
#include<queue>
#include<cstring>
#include<cstdio>
#define MAXN 1001
using namespace std;
struct node
{
     int p,g,h;
     bool operator < (node a) const
     {
          return a.g+a.h<g+h;
     }
};
struct node1
{
     int x,y,w,next;
}edge[MAXN*100],edge1[MAXN*100];
int n,m,i,link[MAXN],link1[MAXN],g[MAXN],s,e,k;
bool used[MAXN];
priority_queue<node> myqueue;
void djikstra()
{
     int i,k,p;
     memset(used,0,sizeof(used));
     memset(g,0x7F,sizeof(g));
     g[e]=0;
     for (p=1;p<=n;p++)
     {
          k=0;
          for (i=1;i<=n;i++)
            if (!used[i] && (!k || g[i]<g[k]))
              k=i;
          used[k]=true;
          k=link1[k];
          while (k)
          {
               if (g[edge1[k].y]>g[edge1[k].x]+edge1[k].w)
                   g[edge1[k].y]=g[edge1[k].x]+edge1[k].w;
               k=edge1[k].next;
          }
     }
     return ;
}
int Astar()
{
     int t,times[MAXN];
     node h,temp;
     while (!myqueue.empty()) myqueue.pop();
     memset(times,0,sizeof(times));
     h.p=s; h.g=0; h.h=0; myqueue.push(h);
     while (!myqueue.empty())
     {
           h=myqueue.top();
           myqueue.pop();
           times[h.p]++;
           if (times[h.p]==k && h.p==e) return h.h+h.g;
           if (times[h.p]>k) continue;
           t=link[h.p];
           while (t)
           {
                 temp.h=h.h+edge[t].w;
                 temp.g=g[edge[t].y];
                 temp.p=edge[t].y;
                 myqueue.push(temp);
                 t=edge[t].next;
           }
     }
     return -1;
}
int main()
{
     scanf("%d%d",&n,&m);
     memset(link,0,sizeof(link));
     memset(link1,0,sizeof(link1));
     for (i=1;i<=m;i++)
     {
          scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].w);
          edge[i].next=link[edge[i].x]; link[edge[i].x]=i;
          edge1[i].x=edge[i].y; edge1[i].y=edge[i].x; edge1[i].w=edge[i].w;
          edge1[i].next=link1[edge1[i].x]; link1[edge1[i].x]=i;
     }
     scanf("%d%d%d",&s,&e,&k);
     if (s==e) k++;
     djikstra();
     printf("%d\n",Astar());
     return 0;
}

 

 

 

 

 

这是一道比较经典的计数问题。题目描述如下: 给定一个 $n \times n$ 的网格图,其中一些格子被标记为障碍。一个连通块是指一些被标记为障碍的格子的集合,满足这些格子在网格图中连通。一个格子是连通的当且仅当它与另一个被标记为障碍的格子在网格图中有公共边。 现在,你需要计算在这个网格图中,有多少个不同的连通块,满足这个连通块的大小(即包含的格子数)恰好为 $k$。 这是一道比较经典的计数问题,一般可以通过计算生成函数的方法来解决。具体来说,我们可以定义一个生成函数 $F(x)$,其中 $[x^k]F(x)$ 表示大小为 $k$ 的连通块的个数。那么,我们可以考虑如何计算这个生成函数。 对于一个大小为 $k$ 的连通块,我们可以考虑它的形状。具体来说,我们可以考虑以该连通块的最左边、最上边的格子为起点,从上到下、从左到右遍历该连通块,把每个格子在该连通块中的相对位置记录下来。由于该连通块的大小为 $k$,因此这些相对位置一定是 $(x,y) \in [0,n-1]^2$ 中的 $k$ 个不同点。 现在,我们需要考虑如何计算这些点对应的连通块是否合法。具体来说,我们可以考虑从左到右、从上到下依次处理这些点,对于每个点 $(x,y)$,我们需要考虑它是否能够与左边的点和上边的点连通。具体来说,如果 $(x-1,y)$ 和 $(x,y)$ 都在该连通块中且它们在网格图中有公共边,那么它们就是连通的;同样,如果 $(x,y-1)$ 和 $(x,y)$ 都在该连通块中且它们在网格图中有公共边,那么它们也是连通的。如果 $(x,y)$ 与左边和上边的点都不连通,那么说明这个点不属于该连通块。 考虑到每个点最多只有两个方向需要检查,因此时间复杂度为 $O(n^2 k)$。不过,我们可以使用类似于矩阵乘法的思想,将这个过程优化到 $O(k^3)$ 的时间复杂度。 具体来说,我们可以设 $f_{i,j,k}$ 表示状态 $(i,j)$ 所代表的点在连通块中,且连通块的大小为 $k$ 的方案数。显然,对于一个合法的 $(i,j,k)$,我们可以考虑 $(i-1,j,k-1)$ 和 $(i,j-1,k-1)$ 这两个状态,然后把点 $(i,j)$ 加入到它们所代表的连通块中。因此,我们可以设计一个 $O(k^3)$ 的 DP 状态转移,计算 $f_{i,j,k}$。 具体来说,我们可以考虑枚举连通块所包含的最右边和最下边的格子的坐标 $(x,y)$,然后计算 $f_{x,y,k}$。对于一个合法的 $(x,y,k)$,我们可以考虑将 $(x,y)$ 所代表的点加入到 $(x-1,y,k-1)$ 和 $(x,y-1,k-1)$ 所代表的连通块中。不过,这里需要注意一个细节:如果 $(x-1,y)$ 和 $(x,y)$ 在网格图中没有相邻边,那么它们不能算作连通的。因此,我们需要特判这个情况。 最终,$f_{n,n,k}$ 就是大小为 $k$ 的连通块的个数,时间复杂度为 $O(n^2 k + k^3)$。 参考代码:
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值