【题解】 P3070 [USACO13JAN]岛游记Island Travels

题面有点坑,翻译内容中没有指明n的范围,通过观察原题面得到$n \leq 15$并大致猜测这是一个状态压缩dp

最小生成树显然不可行,可以举例说明存在某种情况某边要经过两次或更多

对于任意一个岛屿$i$到任意一个岛屿$j$的最短距离显然是固定的,每个岛域之间的距离(不经过其他岛屿)可以用bfs预处理出来,初始状态只要将这个岛屿全部坐标位置全部入队即可

得到了任意两个岛屿之间的直接距离后(当然存在部分岛屿不可互达),在$ n \leq 15$时可以直接用$floyd$求解

现在得到任意两个节点之间的最短距离,显然直接dfs复杂度任然爆炸,dfs求解的复杂度是$O(n!)$

考虑状态压缩dp,设$S$为当前节点是否经过的状态集合,$v$为当前停留的节点,显然有$f(S,v)=min\{ f(S',u)+dis(u,v)\}$其中$S'$中$v$为未达,很明显对于任意一个状态他最多转移$n$个状态

得到时间复杂度为$O(2^nn)$的状压

```
//2019/7/24->Riko->AtNCU->luoguP3070
#include<bits/stdc++.h>
template <typename T> inline void smin (T& x, T y) { if (x > y) x = y;}
//Believing heart is your power
using namespace std;
int getch () {
    int ch = getchar();
    return (ch == '.' or ch == 'S' or ch == 'X') ? ch : getch();
}

const int N = 55;
struct Point {
    int x, y;
    Point (int x, int y) : x(x), y(y) {}
};
struct Len {
    int x, y, l;
    Len (int x, int y, int l) : x(x), y(y), l(l) {}
};
int r, c, n, ind;
int type[N][N], idx[N][N], len[N][N], vis[N][N], hasgone[N];
int f[(1<<15)+64][16], Mx[4] = {0, 0, 1, -1}, My[4] = {1, -1, 0, 0};
queue <Point> Find, Save[N];
queue <Len> Que;

void Getpoints (int x, int y) {
    Find.push(Point(x, y)); Save[ind].push(Point(x, y));
    while (!Find.empty()) {
        Point tem = Find.front(); Find.pop();
        for (int i = 0; i < 4; ++i) {
            int Nx = tem.x+Mx[i];
            int Ny = tem.y+My[i];
            if (idx[Nx][Ny]) continue;
            if (type[Nx][Ny] != 1) continue;
            idx[Nx][Ny] = ind;
            Find.push(Point(Nx, Ny));
            Save[ind].push(Point(Nx, Ny));
        }
    }
}
void Getlen (int id) {
    while (!Save[id].empty()) {
        Point tem = Save[id].front(); Save[id].pop();
        Que.push(Len(tem.x, tem.y, 0)); vis[tem.x][tem.y] = true;
    }
    while (!Que.empty()) {
        Len tem = Que.front(); Que.pop();
        for (int i = 0; i < 4; ++i) {
            int Nx = tem.x+Mx[i];
            int Ny = tem.y+My[i];
            if (!type[Nx][Ny] or vis[Nx][Ny]) continue;
            if (idx[Nx][Ny]) { smin(len[id][idx[Nx][Ny]], tem.l); continue;}
            vis[Nx][Ny] = true;
            Que.push(Len(Nx, Ny, tem.l+1));
        }
    }
}
void work () {
    memset(f, 0x3f, sizeof(f));
    for (int i = 1; i <= ind; ++i) {f[(1<<(i-1))][i] = 0;}
    for (int x = 1; x < (1<<ind); ++x) {
        for (int u = 1; u <= ind; ++u) {
            if (!x&(1<<(u-1))) continue;
            for (int v = 1; v <= ind; ++v) {
                if (x&(1<<(v-1))) continue;
                smin(f[x|(1<<(v-1))][v], f[x][u]+len[u][v]);
            }
        }
    }
    int ans = INT_MAX;
    for (int i = 1; i <= ind; ++i) { smin(ans, f[(1<<ind)-1][i]);}
    printf("%d", ans);
}
void prepare () {
    scanf("%d %d", &r, &c);
    for (int i = 1; i <= r; ++i) {
        for (int j = 1; j <= c; ++j) {
            int ch = getch();
            if (ch == 'X') type[i][j] = 1;
            if (ch == 'S') type[i][j] = 2;
        }
    }
    for (int i = 1; i <= r; ++i) {
        for (int j = 1; j <= c; ++j) {
            if (!idx[i][j] and type[i][j] == 1) {
                idx[i][j] = ++ind;
                Getpoints(i, j);
            }
        }
    }        //prepare for the distance
    memset(len, 0x3f, sizeof(len));
    for (int i = 1; i <= r; ++i) {
        for (int j = 1; j <= c; ++j) {
            if (idx[i][j] and !hasgone[idx[i][j]]) {
                memset(vis, 0, sizeof(vis));
                hasgone[idx[i][j]] = true;
                Getlen(idx[i][j]);
            }
        }
    }        //Get the cloest distance
    for (int i = 1; i <= ind; ++i) {
        for (int j = i+1; j <= ind; ++j) {
            for (int k = i+1; k < j; ++k) {
                smin(len[i][j], len[i][k]+len[k][j]);
                len[j][i] = len[i][j];
            }
        }
    }          //floyd Part
    work();
} int main () { prepare();}
```

转载于:https://www.cnblogs.com/NHDR233/p/11246717.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值