UVA 10944 Nuts for nuts.. (状态压缩dp)

2/22/2017来源:ASP.NET技巧人气:3177

题目描述:So as Ryan and Larry decided that they don’t really taste so good, they realized that there are some nuts located in certain places of the island.. and they love them! Since they’re lazy, but greedy, they want to know the shortest tour that they can use to gather every single nut! Can you help them? Input You’ll be given x, and y, both less than 20, followed by x lines of y characters each as a map of the area, consisting sorely of ‘.’, ‘#’, and ‘L’. Larry and Ryan are currently located in ‘L’, and the nuts are rePResented by ‘#’. They can travel in all 8 adjacent direction in one step. See below for an example. There will be at most 15 places where there are nuts, and ‘L’ will only appear once. Output On each line, output the minimum amount of steps starting from ‘L’, gather all the nuts, and back to ‘L’. Note: In the sample below, Larry and Ryan will go south for a nut, then south again for another nut, then south twice for another nut, and then back where they are.

题目大意:一个人位于地图中 L 的位置,想要得到所有 # 位置的坚果再走回 L 处,所需要的最短步数?

解题思路:这是我做的第一道状态压缩dp,最开始迟迟找不到头绪,不知道该如何确定状态,看了书中的提示,才想到了办法,独立推出了状态转移方程,代码也有很多值得思考的细节。

1、读入数据预处理每一个坚果的位置和L的位置

2、预处理坚果和坚果(或 L )之间的距离

3、状态定义:dp[i][j] -> 在j这种状态下,最后一个收集的坚果为i的情况下所走的最短步数

     状态转移:dp[k][j+(1<<(k-1))] = min( dp[k][j+(1<<(k-1))], dp[i][j] + a[i][k]);//a[i][k]表示第i个坚果和第k个坚果之间的距离

注意: 先循环状态 j,在每个状态下循环 i

这里解释一下为什么是这样的次序,只管来说,问题的关键就在于确定前往若干个#的次序,从 i 到 k 就代表着k的前一位是i的情况,因为需要用dp[i][j] 来更新别的数据,所以一定要保证dp[i][j]是已经更新完成的。所以先循环 i 无法保证 在状态j下所有的排列次序已经走完。

AC代码:

#include <iostream>
#include <cmath>
#include <cstring>
#include <cstdio>

#define Fori(x) for(int i=0;i<x;i++)
#define Forj(x) for(int j=0;j<x;j++)
#define maxn 105
#define inf 0x3f3f3f3f
#define ONES(x) __builtin_popcount(x)
using namespace std;

typedef long long ll ;
const double eps =1e-8;
const int mod = 1000000007;
typedef pair<int, int> P;
const double PI = acos(-1.0);
int dx[4] = {0,0,1,-1};
int dy[4] = {1,-1,0,0};

int n,m;
int ans;
int a[20][20];
char map[30][30];
int dp[30][1<<20];// dp[i][j] -> 在j这种状态下,最后一个收集的坚果为i的情况下所走的最短步数
int x[30],y[30];


int main()
{
    //freopen("test.txt","r",stdin);
    while(cin>>n>>m)
    {
    	int num = 0;
    	for(int i = 1; i<=n; i++){
    		scanf("%s", map[i] + 1);
    		for(int j = 1; j<=m; j++){//预处理每一个坚果的位置和L的位置
    			if(map[i][j]=='#'){
    				x[++num] = i;
    				y[num] = j;
    			}
    			else if(map[i][j]=='L'){
    				x[0] = i;
    				y[0] = j;
    			}
    		}
    	}
    	if(num==0){
    		cout << 0 << endl;
    		continue;
    	}
    	for(int i = 0; i<=num; i++)//预处理坚果和坚果(或 L )之间的距离
    		for(int j = 0; j<=num; j++)
    			a[i][j] = max(abs(x[i]-x[j]),abs(y[i]-y[j]));

    	int up = (1<<num) - 1;
    	memset(dp,inf,sizeof(dp));
    	for(int i = 1; i<=num; i++)
    		dp[i][1<<(i-1)] = a[0][i];

        for(int j = 0; j<up; j++)//要注意i,j两层循环的次序
    	{
    		for(int i = 1; i<=num; i++)
    		{
    			if( (1<<(i-1)) & j)//第i个坚果一定要包含在j这种状态里
    			for(int k = 1; k<=num; k++)
    			{
    				if(k!=i && !(j & (1<<(k-1))))//第k个坚果一定不在j这种状态里
    					dp[k][j+(1<<(k-1))] = min( dp[k][j+(1<<(k-1))], dp[i][j] + a[i][k]);
    			}
    		}
    	}
    	int ans = inf;
    	for(int i = 1; i<=num; i++)
    		ans = min(ans, dp[i][up] + a[i][0]);
    	cout << ans << endl;
    }
    return 0;
}