[3_3_camelot] Shortest path variant (not so easy)

Camelot
IOI 98

Centuries ago, King Arthur and the Knights of the Round Table used to meet every year on New Year's Day to celebrate their fellowship. In remembrance of these events, we consider a board game for one player, on which one chesspiece king and several knight pieces are placed on squares, no two knights on the same square.

This example board is the standard 8x8 array of squares:

The King can move to any adjacent square from  to  as long as it does not fall off the board:

A Knight can jump from  to , as long as it does not fall off the board:

During the play, the player can place more than one piece in the same square. The board squares are assumed big enough so that a piece is never an obstacle for any other piece to move freely.

The player's goal is to move the pieces so as to gather them all in the same square - in the minimal number of moves. To achieve this, he must move the pieces as prescribed above. Additionally, whenever the king and one or more knights are placed in the same square, the player may choose to move the king and one of the knights together from that point on, as a single knight, up to the final gathering point. Moving the knight together with the king counts as a single move.

Write a program to compute the minimum number of moves the player must perform to produce the gathering. The pieces can gather on any square, of course.

PROGRAM NAME: camelot

INPUT FORMAT

Line 1:Two space-separated integers: R,C, the number of rows and columns on the board. There will be no more than 26 columns and no more than 30 rows.
Line 2..end:The input file contains a sequence of space-separated letter/digit pairs, 1 or more per line. The first pair represents the board position of the king; subsequent pairs represent positions of knights. There might be 0 knights or the knights might fill the board. Rows are numbered starting at 1; columns are specified as upper case characters starting with `A'.

SAMPLE INPUT (file camelot.in)

 
8 8
D 4
A 3 A 8
H 1 H 8

The king is positioned at D4. There are four knights, positioned at A3, A8, H1, and H8.

OUTPUT FORMAT

A single line with the number of moves to aggregate the pieces.

SAMPLE OUTPUT (file camelot.out)

 
10

SAMPLE OUTPUT ELABORATION

They gather at B5. 
Knight 1: A3 - B5 (1 move) 
Knight 2: A8 - C7 - B5 (2 moves) 
Knight 3: H1 - G3 - F5 - D4 (picking up king) - B5 (4 moves) 
Knight 4: H8 - F7 - D6 - B5 (3 moves) 
1 + 2 + 4 + 3 = 10 moves. 















my solution: pre-processing distance matrix, enumerate final point and pick-up point, check for all knights, pruning.

trick: 1. maybe no knights 2. unreachable points 3. use current ans to prune

#include <cstdio>
#include <cstring>
#include <climits>
#include <algorithm>

using namespace std;

const int cx[]={-2,-1,1,2,2,1,-1,-2};
const int cy[]={1,2,2,1,-1,-2,-2,-1};

int main()
{
        freopen("camelot.in","r",stdin);
        freopen("camelot.out","w",stdout);
        int r,c;
        scanf("%d%d",&r,&c);
        char buf[10];
        int kx,ky;
        scanf("%s%d",buf,&kx);
        --kx,ky=buf[0]-'A';
        int n=0,px[800],py[800];
        while(scanf("%s%d",buf,&px[n])!=EOF)
                --px[n],py[n++]=buf[0]-'A';
        int w[30][26][30][26];
        for(int i=0;i<r;++i)
                for(int j=0;j<c;++j)
                {
                        int front,rear,qx[800],qy[800],vst[30][26];
                        memset(vst,-1,sizeof(vst));
                        front=0,rear=1,qx[0]=i,qy[0]=j,vst[i][j]=0;
                        while(front<rear)
                        {
                                int x=qx[front],y=qy[front++];
                                for(int k=0;k<8;++k)
                                {
                                        int xx=x+cx[k],yy=y+cy[k];
                                        if(xx>=0 && xx<r && yy>=0 && yy<c && vst[xx][yy]==-1)
                                                qx[rear]=xx,qy[rear++]=yy,vst[xx][yy]=vst[x][y]+1;
                                }
                        }
                        for(int x=0;x<r;++x)
                                for(int y=0;y<c;++y)
                                        w[i][j][x][y]=vst[x][y];
                }
        int ans=(n==0)?0:INT_MAX;
        for(int i=0;i<r;++i)
                for(int j=0;j<c;++j)
                {
                        int lb=0;
                        for(int k=0;k<n;++k)
                                if(w[px[k]][py[k]][i][j]<0)
                                {
                                        lb=-1;
                                        break;
                                }
                                else
                                        lb+=w[px[k]][py[k]][i][j];
                        if(lb<0 || lb>=ans)
                                continue;
                        for(int x=0;x<r;++x)
                                for(int y=0;y<c;++y)
                                {
                                        if(w[x][y][i][j]<0)
                                                continue;
                                        int tmp=max(abs(kx-x),abs(ky-y));
                                        tmp+=w[x][y][i][j]+lb;
                                        for(int k=0;k<n;++k)
                                        {
                                                if(w[px[k]][py[k]][x][y]<0)
                                                        continue;
                                                int t=tmp-w[px[k]][py[k]][i][j]+w[px[k]][py[k]][x][y];
                                                ans=min(ans,t);
                                        }
                                }
                }
        printf("%d\n",ans);
        return 0;
}



standard solution:

This is a modification of the shortest path algorithm. If there was no king, then the shortest path algorithm can determine the distance that each knight must travel to get to each square. Thus, the cost of gathering in a particular square is simply the sum of the distance that each knight must travel, which is fairly simple to calculate.

In order to consider the king, consider a knight which 'picks-up' the king in some square and then travels to the gathering spot. This costs some number of extra moves than just traveling to the gathering spot. In particular, the king must move to the pick-up square, and the knight must travel to this square and then to the final gathering point. Consider the number of extra moves to be the `cost' for that knight to pick-up the king. It is simple to alter the shortest path algorithm to consider picking-up the king by augmenting the state with a boolean flag stating whether the knight has the king or not.

In this case, the cost for gathering at a particular location is the sum of the distance that each knight must travel to get to that square plus the minimum cost for a knight picking up the king on the way.

Thus, for each square, we keep two numbers, the sum of the distance that all the knights that we have seen thus far would have to travel to get to this square and the minimum cost for one of those knights picking up the king on the way (note that one way to 'pick-up' the king is to have the king travel all by itself to the gathering spot). Then, when we get a new knight, we run the shortest path algorithm and add the cost of getting that knight (without picking up the king) to each square to the cost of gathering at that location. Additionally, for each square, we check if the new knight can pick-up the king in fewer moves than any previous knight, and update that value if it can.

After all the knights have been processed, we determine the minimum over all squares of the cost to get to that square plus the additional cost for a knight to pick-up the king on its way to that square.

 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/* "infinity"... > maximum distance possible (for one knight) */
#define MAXN 10400

/* maximum number of rows */
#define MAXR 40 

/* maximum number of columns */
#define MAXC 26 

/* cost of collecting all knights here */
int cost[MAXC][MAXR]; 

/* cost of getting a knight to collect the king */
int kingcost[MAXC][MAXR];

/* distance the king must travel to get to this position */
int kdist[MAXC][MAXR];

/* distance to get for current knight to get to this square */
/* third index: 0 => without king, 1 => with king */
int dist[MAXC][MAXR][2]; 

/* number of rows and columns */
int nrow, ncol;

int do_step(int x, int y, int kflag) {
    int f = 0; /* maximum distance added */
    int d = dist[x][y][kflag]; /* distance of current move */

  /* go through all possible moves that a knight can make */
    if (y > 0) {
        if (x > 1)
             if (dist[x-2][y-1][kflag] > d+1) {
                 dist[x-2][y-1][kflag] = d+1;
                 f = 1;
             }
            if (x < ncol-2) {
                if (dist[x+2][y-1][kflag] > d+1) {
	            dist[x+2][y-1][kflag] = d+1;
	            f = 1;
	        }
            }
            if (y > 1) {
                if (x > 0)
	            if (dist[x-1][y-2][kflag] > d+1) {
	                dist[x-1][y-2][kflag] = d+1;
	                f = 1;
	            }
	        if (x < ncol-1)
	            if (dist[x+1][y-2][kflag] > d+1) {
	                dist[x+1][y-2][kflag] = d+1;
	                f = 1;
	            }
            }
    }
    if (y < nrow-1) {
        if (x > 1)
            if (dist[x-2][y+1][kflag] > d+1) {
                dist[x-2][y+1][kflag] = d+1;
                f = 1;
            }
            if (x < ncol-2) {
                if (dist[x+2][y+1][kflag] > d+1) {
                    dist[x+2][y+1][kflag] = d+1;
                    f = 1;
                }
            }
        if (y < nrow-2) {
            if (x > 0)
                if (dist[x-1][y+2][kflag] > d+1) {
                    dist[x-1][y+2][kflag] = d+1;
                    f = 1;
                }
            if (x < ncol-1)
                if (dist[x+1][y+2][kflag] > d+1) {
                    dist[x+1][y+2][kflag] = d+1;
                    f = 1;
                }
        }
    }

/* also check the 'pick up king here' move */
    if (kflag == 0 && dist[x][y][1] > d + kdist[x][y]) {
        dist[x][y][1] = d + kdist[x][y];
        if (kdist[x][y] > f) f = kdist[x][y];
    }
    return f; /* 1 if simple knight move made, 0 if no new move found */
}

void calc_dist(int col, int row) {
    int lv, lv2;	/* loop variables */
    int d;		/* current distance being checked */
    int max; 		/* maximum finite distance found so far */
    int f; 		/* temporary variable (returned value from do_step */

/* initiate all positions to be infinite distance away */
    for (lv = 0; lv < ncol; lv++)
        for (lv2 = 0; lv2 < nrow; lv2++)
            dist[lv][lv2][0] = dist[lv][lv2][1] = MAXN;

/* starting location is zero w/o king, kdist[col][row] with king */
    dist[col][row][0] = 0;
    max = dist[col][row][1] = kdist[col][row];

    for (d = 0; d <= max; d++) { /* for each distance away */
        for (lv = 0; lv < ncol; lv++)
            for (lv2 = 0; lv2 < nrow; lv2++) {
				/* for each position that distance away */
                if (dist[lv][lv2][0] == d) {
				 /* update with moves through this square */
                    f = do_step(lv, lv2, 0);
                    if (d + f > max)     /* update max if necessary */
			max = d + f;
                 }

                 if (dist[lv][lv2][1] == d) {
			/* same as above, except this time knight has king */
                     f = do_step(lv, lv2, 1);
                     if (d + f > max) max = d + f;
                 }
            }
    }
}

int main(int argc, char **argv) {
    FILE *fout, *fin;
    char t[10];
    int pr, pc;
    int lv, lv2;
    int i, j;

    if ((fin = fopen("camelot.in", "r")) == NULL) {
        perror ("fopen fin");
        exit(1);
    }
    if ((fout = fopen("camelot.out", "w")) == NULL) {
        perror ("fopen fout");
        exit(1);
    }

    fscanf (fin, "%d %d", &nrow, &ncol);
    fscanf (fin, "%s %d", t, &pr);
    pc = t[0] - 'A';
    pr--;

  /* Calculate cost of moving king from starting position to
   * each board position.  This is just the taxi-cab distance */
   for (lv = 0; lv < ncol; lv++)
       for (lv2 = 0; lv2 < nrow; lv2++) {
           i = abs(pc-lv);
           j = abs(pr-lv2);
           if (i < j) i = j;
           kingcost[lv][lv2] = kdist[lv][lv2] = i;
       }

    while (fscanf (fin, "%s %d", t, &pr) == 2) { /* for all knights */
        pc = t[0] - 'A';
        pr--;

        /* calculate distances */
        calc_dist(pc, pr);

        for (lv = 0; lv < ncol; lv++)
            for (lv2 = 0; lv2 < nrow; lv2++) {
                /* to collect here, we must also move knight here */
                cost[lv][lv2] += dist[lv][lv2][0];

	        /* check to see if it's cheaper for the new knight to
	           pick the king up instead of whoever is doing it now */
	        if (dist[lv][lv2][1] - dist[lv][lv2][0] < kingcost[lv][lv2]) {
	            kingcost[lv][lv2] = dist[lv][lv2][1] - dist[lv][lv2][0];
	        }
            }
    }
    /* find best square to collect in */
    pc = cost[0][0] + kingcost[0][0];

    for (lv = 0; lv < ncol; lv++)
        for (lv2 = 0; lv2 < nrow; lv2++)
            if (cost[lv][lv2] + kingcost[lv][lv2] < pc) /* better square? */
                pc = cost[lv][lv2] + kingcost[lv][lv2]; 
  fprintf (fout, "%i\n", pc);
  return 0;
}







官方题解还是比较靠谱的。他讲话比较绕,大意是这样:

假设集合点为T,那么总的步数可以看做所有骑士先沿最短路走到T,然后把其中一个骑士的最短路改成中途接王的另一条路,那么总步数要多出新路的步数减去原来的最短路,称为额外步数。我们只要对每个点维护最短路的总长和额外步数的最小值。在加进一个新骑士时算出他到每个点的不接王最短路和接王最短路即可维护。

问题是怎么算“接王最短路”呢?题解没说,去看标程好像是每次找最近的扩展,感觉做了很多无用功。

我们考虑骑士接王走到点v,要么是从周围带着王走过来,要么是走到v之后在点v接上王。于是可以把每个点i拆成两个点xi和yi,xi表示不带王,yi表示带王。x层点之间、y层点之间还是按马的移动方式连边,长度为1。对应的xi和yi之间也要连边,表示在点i带上王,这时骑士不用动,但是王要屁颠屁颠跑到点i来,所以xi到yi的边长度为王跑到点i所需的步数。每个骑士跑一遍最短路,dis[xi]就是到点i的不接王最短路,dis[yi]就是接王最短路。由于边数只有8n(准确的说是17n),就用人民群众喜闻乐见的SPFA来求,我相信它是O(kE)的,于是我相信总的复杂度是O(n^2)的。




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值