SPFA维护dp——【NOI2014模拟7.11】挖宝藏

Description

SPFA维护dp——【NOI2014模拟7.11】挖宝藏_第1张图片

InputSPFA维护dp——【NOI2014模拟7.11】挖宝藏_第2张图片

Output

输出一个整数,为矿工获得所有宝藏的最小代价。

Sample Input

2 2 2
10 9
10 10
10 1
10 10
1 1 1
1 2 2

Sample Output

30
SPFA维护dp——【NOI2014模拟7.11】挖宝藏_第3张图片

题解:

据说h=1时与WC 2008 的 游览计划完全相同。
先看看h=1时怎么做。

设f[x][y][s]表示到(x,y)这个点经过了s这个集合的宝藏的最小代价。

f[x][y][s] = min{
1. f[x][y][s]+f[x][y][s/s](ss)
2. f[x][y1][s]+a[x][y]
3. f[x][y+1][s]+a[x][y]
4. f[x1][y][s]+a[x][y]
5. f[x+1][y][s]+a[x][y]
}

我们惊奇地发现2-5方程有后效性,怎么办?
不要方,我们把它YY成一个有向图,然后你发现SPFA展现神威了。

从小到大枚举S,先把第一条方程给维护了,每个点一开始的f[x][y][s]就确定了,但是不一定是最短的,接着把每个点都加进队列,跑SPFA求最小值。

但是,有人可能会有疑问,万一我们已经走过了(x,y),又走了过来,不就重复算了a[x][y]吗?是的,我们一定会遇到这种情况,把这想象成一个无向的图,我们要跑的是最短路,重复走相当于走过这条边再绕回来,你在最短路中会这么走吗?

那么h=1的情况就解决了。

那如果有多层呢?
在求完每一层后,对于每个点(x,y),从它这儿下去的代价就是f[x][y][Full],对于从上一层传下来的,我们可以给它再开一个宝藏位,表示是否携带了上一层传下来的代价,因为二者只用取其一,这和宝藏是一个道理。

预处理,就把所有已知的扔进去,就可以不管了,因为SPFA功能强大。

Code:

#include
#include
#include
#include
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define fd(i, x, y) for(int i = x; i >= y; i --)
#define min(a,b) ((a) < (b) ? (a) : (b))

using namespace std;

int move[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
int h,n,m,a[11][11][11], K[11];
struct node {
    int x, y;
}b[11][11];
int o, f[2][12][12][1025];
int a2[12], d[10][1025], d0[12], s1[1025];
int t[5000000][2], bz[12][12], bx[1025];

void Init() {
    scanf("%d %d %d", &h, &n, &m);
    fo(k, 1, h) fo(i, 1, n) fo(j, 1, m) scanf("%d", &a[k][i][j]);
    fo(k, 1, h) {
        scanf("%d", &K[k]);
        fo(i, 1, K[k])
            scanf("%d %d", &b[k][i].x, &b[k][i].y);
    }

    a2[0] = 1; fo(i, 1, 11) a2[i] = a2[i - 1] << 1;
    d0[0] = 1; d[0][1] = 0; s1[0] = 0;
    fo(i, 1, 1023) {
        fo(j, 0, 9) if(i & a2[j]) s1[i] ++;
        d[s1[i]][++ d0[s1[i]]] = i;
    }
}

void Spfa() {
    fd(c, h, 1) {
        o = !o;
        memset(f[o], 60, sizeof(f[o]));
        fo(x, 1, n)
            fo(y, 1, m) {
                f[o][x][y][0] = a[c][x][y];
                f[o][x][y][1] = f[!o][x][y][a2[K[c + 1] + 1] - 1] + a[c][x][y];
            }
        fo(i, 1, K[c]) {
            int x = b[c][i].x, y= b[c][i].y;
            f[o][x][y][a2[i]] = a[c][x][y];
            f[o][x][y][a2[i] + 1] = f[!o][x][y][a2[K[c + 1] + 1] - 1] + a[c][x][y];
        }
        memset(bx, 0, sizeof(bx));
        fo(g, 0, K[c] + 1) {
            fo(gg, 1, d0[g]) {
                int nowc = d[g][gg];
                if(nowc > a2[K[c] + 1] - 1) continue;
                bx[nowc] = 1;
                fo(othc, 0, 1023) if((othc & nowc) == othc) {
                    int newc = othc ^ nowc;
                    fo(x, 1, n) fo(y, 1, m)
                        f[o][x][y][nowc] = min(f[o][x][y][nowc], f[o][x][y][newc] + f[o][x][y][othc] - a[c][x][y]);
                }
                int st = 0, en = 0;
                fo(x, 1, n) fo(y, 1, m) t[++ en][0] = x, t[en][1] = y, bz[x][y] = 1;
                while(st < en) {
                    int x = t[++st][0], y = t[st][1];
                    fo(k, 0, 3) {
                        int l = x + move[k][0], r = y + move[k][1];
                        if(l > 0 && l <= n && r > 0 && r <= m) {
                            if(f[o][x][y][nowc] + a[c][l][r] < f[o][l][r][nowc]) {
                                f[o][l][r][nowc] = f[o][x][y][nowc] + a[c][l][r];
                                if(!bz[l][r])
                                    bz[l][r] = 1, t[++en][0] = l, t[en][1] = r;
                            }
                        }
                    }
                    bz[x][y] = 0;
                }
            }
        }
    }
}

int main() {
    freopen("treasure.in", "r", stdin);
    freopen("treasure.out", "w", stdout);
    Init();
    Spfa();
    int ans = 1e9;
    fo(x, 1, n) fo(y, 1, m) ans = min(ans , f[o][x][y][a2[K[1] + 1] - 1]);
    printf("%d",ans);
}

你可能感兴趣的:(动态规划,最短路径,SPFA)