POJ1185(炮兵阵地) 状压DP

每一行的状态是取决于上一行和上上一行的,所以每次更新的时候需要记录当前行的状态和下一行的状态,然后再进行递推。。。不过估算了一下复杂度是10的11次方,吓得我都没敢写啊!!!看了一下别人的博客,居然真有这样写过的,于是就自己实现一边啦【最后看了以下讨论版,处理一下复杂度可以降低的,最后贴讨论版代码】

思路:令P为1,H为0。用一个数组存下每一行的状态。

1.先有数组num[] 存下各个状态的1的个数。

2.设dp[i][j][k] 为考虑了前i-1行,第i-1行的状态为j,第i-2行的状态为k的最大炮兵数。

那么有递推式dp[i][j][k] = max(dp[i][j][k],dp[i-1][k][t] + num[j]);且j,k,t满足(j & k) = 0,(k & t) = 0,(j & t) = 0;

然后考虑特例n=1和边界n=2。

3.直接开数组应该会爆的,所以滚动数组实现。


我的代码:

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

using namespace std;
const int maxn = 105;
const int maxm = 10;

int n,m,S[maxn];
int dp[2][1<<maxm][1<<maxm];
int num[1<<maxm];

void init(){
    int tmp,cnt;
    for(int i=0;i<(1<<maxm);i++){
        cnt = 0;tmp = i;
        while(tmp){
            if(tmp & 1) cnt++;
            tmp >>= 1;
        }
        num[i] = cnt;
    }
}

bool islegal(int i,int x){
    if((~S[i]) & x) return false;
    if((x & (x << 1)) || (x & (x << 2)) || (x & (x >> 1)) || (x & (x >> 2))) return false;
    return true;
}

int main(){
    init();
    scanf("%d%d",&n,&m);getchar();
    for(int i=0;i<n;i++){
        char ch;
        for(int j=m-1;j>=0;j--){
            ch = getchar();
            if(ch == 'P') S[i] += (1 << j);
        }
        getchar();
    }
    int top = (1 << m);
    if(n == 1){
        int res = 0;
        for(int j=0;j<top;j++) if(islegal(0,j)) res = max(res,num[j]);
        printf("%d\n",res);
        return 0;
    }
    for(int j=0;j<top;j++){
        if(!islegal(1,j)) continue;
        for(int k=0;k<top;k++){
            if(islegal(0,k) && !(j & k))
                dp[1][j][k] = num[j] + num[k];
        }
    }
    for(int i=2;i<n;i++){
        int v = i & 1,u = (i + 1) & 1;
        for(int j=0;j<top;j++){
            if(!islegal(i,j)) continue;
            for(int k=0;k<top;k++){
                if(!islegal(i-1,k) || (j & k)) continue;
                for(int t=0;t<top;t++){
                    if((t & j) || (t & k)) continue;
                    dp[v][j][k] = max(dp[v][j][k],dp[u][k][t] + num[j]);
                }
            }
        }
    }
    int u = (n - 1) & 1;
    int res = 0;
    for(int j=0;j<top;j++){
        for(int k=0;k<top;k++){
            res = max(res,dp[u][j][k]);
        }
    }
    printf("%d\n",res);
    return 0;
}




讨论版上某神牛的代码orz:

/*这题关键就是对状态的理解,由于每个炮能打2格的位置
我们可以先dfs出,每一行可能摆放的各种炮兵状态,canState[],canNum[]
经预先计算得出,m=10时最大的状态数为60
其次,没放过一行时,该行有两个有意义的状态。
一个是当前行的状态,一个是他上一行的状态。
这样一来,每一行就有60*60种状态。
我们可以定义dp[l][i][j],l表示第几层,i表示当前层的状态,j表示上一层的状态
那么 dp[l][i][j] = max{dp[l-1][j][k]} + canNum[i]
canNum[i]表示dfs预处理得出的第i个状态对应的编号
剩下的,就跟背包的更新极为类似了。
*/


#include<cstdio>
#include<cstring>

#define compatible(a,b) (((a)&(b))==0)
#define MN 110
#define MS 61
#define MM 11


/*dfs函数和它用到的全局变量
dfs函数,用于打表出每行m个位置时
各种可行的状态,数字为为1表示是炮*/
int canState[MS];
int canNum[MS];
int len;
int hillp;
void dfs(int loc,int num,int m);
/******************************/
/*input函数和它用到的全局变量
此函数功能为,输入数据,并处理
得到各层的hill,数位为1表示此位置不能放炮*/
char coteau[MN][MM];
int hill[MN];
void input(int n,int m);
/*****************************/
/*dpSolve函数和它所用到的全局变量
该函数功能是利用动态规划,计算出各层
各状态中,最多能放多少炮*/
int dpAmount[MN][MS][MS];
bool dpVisit[MN][MS];
void dpSolve(int n,int m);
/*************************/
int findmx(int n);//查找dpAmount中的最大值
int main()
{
	int n,m;
	while(~scanf("%d%d",&n,&m))
	{
		input(n,m);
		dpSolve(n,m);
		printf("%d\n",findmx(n));
	}
	return 0;
}
void input(int n,int m)
{
	int i,tmp;
	char *p;
	hill[0] = -2;
	for(i=1;i<=n;i++)
	{
		scanf("%s",coteau[i]);
		tmp = 0;
		for(p=coteau[i];*p;p++)
		{
			tmp<<=1;
			if(*p=='H')
				tmp|=1;
		}
		hill[i]=tmp;
	}
}


void dfs(int loc,int num,int m)
{
	int i;
	canNum[len] = num;
	canState[len++]=hillp;
	if(loc>=m-3)
	{
		return;
	}
	for(i=loc+3;i<m;i++)
	{
		hillp|=(1<<i);
		dfs(i,num+1,m);
		hillp&=(~(1<<i));
	}
}


void dpSolve(int n,int m)
{
	int i,j,k,l,mx;
	//初始化dp所需的各元素
	len=hillp=0;
	dfs(-3,0,m);
	memset(dpAmount,-1,sizeof(dpAmount));
	memset(dpVisit,false,sizeof(dpVisit));
	dpAmount[0][0][0] = 0;dpVisit[0][0]=true;
	//进行dp
	for(l=1;l<=n;l++)
	{
		for(i=0;i<len;i++)
		{
			if(compatible(canState[i],hill[l]))
			{
				for(j=0;j<len;j++)
				{
					if((!compatible(canState[i],canState[j]))||
						(!dpVisit[l-1][j]))continue;
					mx=-1;
					for(k=0;k<len;k++)
					{
						if(dpAmount[l-1][j][k]>=0&&
							compatible(canState[i],canState[k])&&
							mx<dpAmount[l-1][j][k])
						{
							mx = dpAmount[l-1][j][k]; 
						}
					}
					if(mx>=0)
					{
						dpAmount[l][i][j] = canNum[i] + mx;
						dpVisit[l][i] = true;
					}
				}
			}
		}
	}
}
int findmx(int n)
{
	int mx=0;
	int i,j,l;
	for(l=1;l<=n;l++)
	{
		for(i=0;i<len;i++)
		{
			for(j=0;j<len;j++)
			{
				if(mx<dpAmount[l][i][j])
					mx = dpAmount[l][i][j];
			}
		}
	}
	return mx;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 给出一个$n\times m$的矩阵,每个位置上有一个非负整数,代表这个位置的海拔高度。一开始时,有一个人站在其中一个位置上。这个人可以向上、下、左、右四个方向移动,但是只能移动到海拔高度比当前位置低或者相等的位置上。一次移动只能移动一个单位长度。定义一个位置为“山顶”,当且仅当从这个位置开始移动,可以一直走到海拔高度比它低的位置上。请问,这个矩阵中最多有多少个“山顶”? 输入格式 第一行两个整数,分别表示$n$和$m$。 接下来$n$行,每行$m$个整数,表示整个矩阵。 输出格式 输出一个整数,表示最多有多少个“山顶”。 样例输入 4 4 3 2 1 4 2 3 4 3 5 6 7 8 4 5 6 7 样例输出 5 算法1 (递归dp) $O(nm)$ 对于这道题,我们可以使用递归DP来解决,用$f(i,j)$表示以$(i,j)$为起点的路径最大长度,那么最后的答案就是所有$f(i,j)$中的最大值。 状态转移方程如下: $$ f(i,j)=\max f(x,y)+1(x,y)是(i,j)的下一个满足条件的位置 $$ 注意:这里的状态转移方程中的$x,y$是在枚举四个方向时得到的下一个位置,即: - 向上:$(i-1,j)$ - 向下:$(i+1,j)$ - 向左:$(i,j-1)$ - 向右:$(i,j+1)$ 实现过程中需要注意以下几点: - 每个点都需要搜一遍,因此需要用双重for循环来枚举每个起点; - 对于已经搜索过的点,需要用一个数组$vis$来记录,防止重复搜索; - 在进行状态转移时,需要判断移动后的点是否满足条件。 时间复杂度 状态数为$O(nm)$,每个状态转移的时间复杂度为$O(1)$,因此总时间复杂度为$O(nm)$。 参考文献 C++ 代码 算法2 (动态规划) $O(nm)$ 动态规划的思路与递归DP类似,只不过转移方程和实现方式有所不同。 状态转移方程如下: $$ f(i,j)=\max f(x,y)+1(x,y)是(i,j)的下一个满足条件的位置 $$ 注意:这里的状态转移方程中的$x,y$是在枚举四个方向时得到的下一个位置,即: - 向上:$(i-1,j)$ - 向下:$(i+1,j)$ - 向左:$(i,j-1)$ - 向右:$(i,j+1)$ 实现过程中需要注意以下几点: - 每个点都需要搜一遍,因此需要用双重for循环来枚举每个起点; - 对于已经搜索过的点,需要用一个数组$vis$来记录,防止重复搜索; - 在进行状态转移时,需要判断移动后的点是否满足条件。 时间复杂度 状态数为$O(nm)$,每个状态转移的时间复杂度为$O(1)$,因此总时间复杂度为$O(nm)$。 参考文献 C++ 代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值