题目链接
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;
}