【算法设计与数据结构】URAL 1152.False Mirrors(状态压缩dp+dfs)

题目链接

http://acm.timus.ru/problem.aspx?space=1&num=1152

题目大意

n个阳台(3<=n<=20)围成一个圈,每个阳台里有若干只怪兽(1~100)。中间有一个勇士,他每次开炮摧毁阳台i,则与i相邻的2个阳台也会跟着被摧毁(第一个阳台与第n个阳台是相邻的),摧毁后相应阳台里的怪兽都会死去。每一次开炮之后,幸存的怪兽会对他展开一次反击,每只怪兽会对他造成1点伤害值。求消灭所有怪兽时,勇士受到的最小伤害。

思路

对于每个阳台,只有已经被摧毁和未被摧毁两种可能,而且n最大为20,所以可以考虑用状态压缩dp。我们使用dp[sta]来表示sta状态下发起进攻后取得的最少伤害值,则dp[sta] = min{dp[next_sta]+damage},next_sta表示在状态sta下发起攻击后得到的状态,damage则表示这一轮受到的伤害值。

算法步骤

1) 初始化边界条件:
memset(dp, INF, sizeof(dp));
dp[0] = 0;

2) 根据状态转移方程动态规划求解:
状态转移方程为:
dp[sta] = min{dp[next_sta]+damage};
其中dp[next_sta]递归地求解。

源程序

#include<iostream>
#include<algorithm>
#include<cstring> 

using namespace std;

const int MAXN = 25;

const int INF = 0x3f3f3f3f;

int a[MAXN];

int dp[(1<<20)+5];

int n;

//sta从低位到高位表示位置1~n的状态,0表示已摧毁,1表示未摧毁 
int dfs(int sta)
{
    if (dp[sta] != INF)
        return dp[sta];
    //每一层遍历每种可能的摧毁方式 
    for (int i = 1; i <= n; i++)
    {
        //如果第i位已经被摧毁了,那么它的左右也必定被摧毁,故寻找未摧毁的。 
        if(sta & (1<<(i-1)))  
        {  
            int l, r, tmp;
            tmp = sta;
            //找出i的左右邻居 
            if (i == 1)
                l = n;
            else 
                l = i-1;
            if (i == n)
                r = 1;
            else
                r = i+1;  
            //攻击i 
            tmp -= (1<<(i-1));
            //如果左邻居存在,攻击 
            if(tmp&(1<<(l-1)))  
                tmp -= (1<<(l-1));
            //如果右邻居存在,攻击  
            if(tmp&(1<<(r-1)))  
                tmp -= (1<<(r-1));
            int damage = 0; 
            //求出这一轮受到的伤害值 
            for(int j = 1; j <= n; j++)  
                if(tmp & (1<<(j-1)))
                    damage += a[j];
            //状态转移 
            dp[sta] = min(dp[sta],dfs(tmp)+damage);  
                }  
    }   
    return dp[sta];
}

int main()
{
    cin>>n;
    for (int i = 1; i <= n; i++)
        cin>>a[i];
    memset(dp, INF, sizeof(dp));
    dp[0] = 0;
    cout<<dfs((1<<n)-1)<<endl;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值