题目:http://poj.org/problem?id=2288
这是一道状态压缩dp
看到这题知道是用动态规划,猜想是用三维数组的,,但是不知道是状态压缩dp,今天刚学这个状态dp,这种状态压缩dp挺神奇的,只是怎么表示状态真难搞,花了一下午的时间看了几个大佬的博客,基本懂了,我真是太菜了~~
进入正题
首先分析问题
1.每个岛屿有一个值
2.两个相连的边要添加两个岛屿值相乘得到的值
3. 构成三角形,第一个岛屿和最后一个岛屿的边值不要计算到结果里去
4. 哈密顿路径值 = 所有岛屿的值 + 加上构成三角形的岛屿的边值(注意第三条)+三个岛屿值的乘积
5. 找出最大的哈密顿路径值和方案数
int dp[1 << N][N][N]; //存储岛屿之间的哈密顿路径值 第一维表示状态
ll num[1 << N][N][N]; //存储方案数 第一维表示状态
for(int i = 0;i<n;i++) //这里是把两个岛屿相连的哈密顿路径值算出来,因为后面计算要用
for (int j = 0; j < n; j++) {
if (i != j && g[i][j]) { //判断岛是否相连
//这里的位运算就是关键,,,你得把他看成二进制根据1出现的位置来表示哪两个岛屿相连
dp[(1 << i) | (1 << j)][i][j] = a[i] + a[j] + a[i] * a[j]; //把两个岛屿的值以及两个岛屿的值乘积加起来
num[(1 << i) | (1 << j)][i][j] = 1;//方案数是1
}
}
上面这部分比较关键
AC代码:
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
typedef long long ll;
const int N = 13; //最大岛屿数量
int dp[1 << N][N][N]; //存储岛屿之间的哈密顿路径值
ll num[1 << N][N][N]; //存储方案数
int g[N][N];
int main() {
int q;
cin >> q;
while (q--) {
//初始化
memset(dp, -1, sizeof(dp));
memset(num, 0, sizeof(num));
memset(g, 0, sizeof(g));
int n, m;
cin >> n >> m; //岛屿数量,桥梁数量
int a[N];
for (int i = 0; i < n; i++) //每个岛屿的值
cin >> a[i];
int u, v;
for (int i = 0; i < m; i++) { //各岛屿相连的边
cin >> u >> v;
u--, v--; //这里要-1,因为后面有位运算,要下标一致
g[u][v] = g[v][u] = 1;
}
for(int i = 0;i<n;i++) //这里是把两个岛屿相连的哈密顿路径值算出来,因为后面计算要用
for (int j = 0; j < n; j++) {
if (i != j && g[i][j]) { //判断岛是否相连
//这里的位运算就是关键,,,你得把他看成二进制根据1出现的位置来表示哪两个岛屿相连
dp[(1 << i) | (1 << j)][i][j] = a[i] + a[j] + a[i] * a[j]; //把两个岛屿的值以及两个岛屿的值乘积加起来
num[(1 << i) | (1 << j)][i][j] = 1;//方案数是1
}
}
if (n == 1) { //只有一个岛屿的时候,特判
cout << a[0] <<" 1"<< endl;
continue;
}
for (int state = 0; state < (1 << n); state++) { //把所有状态遍历一次
for (int i = 0; i < n; i++) { //找三角形的第一个岛屿 ,,在多个岛屿中,指的是倒数第二个岛屿
if ((state & (1 << i)) != 0) { //通过与运算可以知道当前岛屿有没有在当前状态里面
for (int j = 0; j < n; j++) { //找三角形的第二个岛屿,在多个岛屿中,指的是倒数第一个岛屿
if (g[i][j] && i != j && (state & (1 << j))!=0 && dp[state][i][j] != -1) { //两个岛屿是否相连,是不是同一个岛屿,第二个岛屿在不在当前状态下,在当前状态下dp值是否计算过
for (int k = 0; k < n; k++) { //找第三个岛屿,在多个岛屿中,指的是倒数第0个岛屿
if (i != k && j != k && (state & (1 << k)) == 0 && g[j][k]) { //判断第三个与另外两个岛屿是否相等,通过与运算判断第三个岛屿在不在当前状态下,一定要不在
int temp = dp[state][i][j] + a[k] + a[j] * a[k]; //把前面计算了两个岛屿的dp值+当前岛屿的值+第二个岛屿和第三个岛屿的值乘积全都加起来
if (g[i][k]) //看第一个岛屿与第三个岛屿是否相连,如果相连就构成三角形
temp += a[i] * a[j] * a[k]; //构成三角形那么就要再加上三个岛屿值的乘积
if (dp[(state | (1 << k))][j][k] < temp) { //注意这里的下标,状态,通过或运算可以得到三个岛屿的状态,而后面的变成了第二个岛屿和第三个岛屿,再比较当前状态下这三个岛屿的dp值是否大于刚才计算的temp
dp[(state | (1 << k))][j][k] = temp;
num[(state | (1 << k))][j][k] = num[state][i][j];
}
else if (dp[(state | (1 << k))][j][k] == temp) //相等的话那就只用把方案数加起来
num[(state | (1 << k))][j][k] += num[state][i][j];
}
}
}
}
}
}
}
int ans1 = 0; //哈密顿路径最大值
ll ans2 = 0; //方案数
for(int i = 0;i<n;i++)
for (int j = 0; j < n; j++) {
if (dp[(1<<n)-1][i][j] > ans1) {
ans1 = dp[(1 << n) - 1][i][j]; //这部分的位运算就是找到每一位都是1的状态,也就是所有岛屿都考虑了的状态
ans2 = num[(1 << n) - 1][i][j];
}else if(dp[(1 << n) - 1][i][j] == ans1)
ans2 += num[(1 << n) - 1][i][j];
}
cout << ans1 << " " << (ans2 >> 1) << endl; //方案数要除以2,因为一头一尾都加了,就表示方案数家里两遍
}
return 0;
}