POJ 2449Remmarguts' Date (A* 求K短路)

题目链接

题意 : 求一张有向图S->T的K短路。

思路 : 经典题目。 K短路最简单并且最暴力的方法是从起始点S爆搜下去, 把到达的x的距离d[x]不停的加入到一个优先队列中去, 最后然后每次取出最短距离节点u,然后cnt[u]++一直到到达了u = T && cnt[u] = k结束搜索。 这个方法复杂度似乎是O(M*K),对于这道题目是超时或者爆内存的 = = 。

网上看了下比较好也比较容易写的方法是A*: 评估函数f(x) = g(x) + h(x)   ( f(x) 小的优先级高, 先搜),g(x)表示的是从S到x现在搜索到的距离,不断用最短的f(x),更新, h(x)表示从x到T的最短距离(建立反向边用Dijkstra或者spfa算法预处理出来),这样利用A*优化搜索可以AC这道题目。

#include 
   
   
    
    
#include 
    
    
     
     
#include 
     
     
      
      
#include 
      
      
       
       

using namespace std;

const int maxn = 100005;
const int INF = 12842184;

struct Dist{
    int d, x;
    Dist(){}
    Dist(int a, int b) : d(a), x(b){}
    bool operator < (const Dist &cmp)const {
        return d > cmp.d;
    }
};

struct astar{
    int g, h, x;
    astar(){}
    astar(int a, int b, int c) : g(a), h(b), x(c){}
    bool operator < (const astar &cmp)const {
        return g + h > cmp.g + cmp.h;
    }
};

struct Edge{
    int to, c, next;
}edge[2][maxn];

int n, m, k, E1, E2, sx, ex;
int d[maxn], vis[maxn], cnt[maxn], head[2][maxn];

void init(){
    E1 = 0, E2 = 0;
    memset(head, -1, sizeof(head));
}

void add_edge(int u, int to, int c, int &E, Edge *edge, int *head){
   edge[E].to = to;
   edge[E].c = c;
   edge[E].next = head[u];
   head[u] = E++;
}

void Dijkstra(){
    fill(d+1, d+n+1, INF); d[ex] = 0;
    memset(vis, 0, sizeof(vis));
    priority_queue
       
       
         q; q.push(Dist(d[ex], ex)); while (!q.empty()){ Dist tmp = q.top(); q.pop(); int u = tmp.x; if (vis[u])continue; vis[u] = 1; for (int i = head[1][u]; i != -1; i = edge[1][i].next){ int to = edge[1][i].to, c = edge[1][i].c; if (d[to] > d[u] + c){ d[to] = d[u] + c; q.push(Dist(d[to], to)); } } } return ; } int A_star(){ Dijkstra(); if (sx == ex)k++;// 这是一个trick priority_queue 
        
          q; memset(cnt, 0, sizeof(cnt)); q.push(astar(0, d[sx], sx)); while (!q.empty()){ astar tmp = q.top(); q.pop(); int u = tmp.x; cnt[u]++; if (cnt[u] > k)continue; if (u == ex && cnt[u] == k)return tmp.g; for (int i = head[0][u]; i != -1; i = edge[0][i].next){ int to = edge[0][i].to, c = edge[0][i].c; q.push(astar(tmp.g+c, d[to], to)); } } return -1; } int main(){ scanf("%d%d", &n, &m); init(); while (m--){ int a, b, c; scanf("%d%d%d", &a, &b, &c); add_edge(a, b, c, E1, edge[0], head[0]); add_edge(b, a, c, E2, edge[1], head[1]); } scanf("%d%d%d", &sx, &ex, &k); printf("%d\n", A_star()); return 0; } 
         
       
      
      
     
     
    
    
   
   






PS  :虽然AC了但是这种方法给我的感觉还是很暴力(但是据说一般都是可以做了的), 汗....    k - 短路问题网上还有一种叫做Yen的算法, 但我不会, 下次补齐。:(


这是一道比较经典的计数问题。题目描述如下: 给定一个 $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)$。 参考代码:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值