版权声明:本文为DyingShu原创文章,转载请注明出处哦。 https://blog.csdn.net/DyingShu/article/details/82707531
借鉴了一下这位dalao的题解
NOIP2017的D2T2,状压DP。
看到n的范围很容易想到是状压DP,但是怎么转移就很有趣了
设
表示最大深度为
时,已开通的点状态为
时的最小花费。
每次转移时,枚举不在集合中且可以转移的点,枚举子集进行转移。
用
与
中所有点的更新代价更新
。
为什么只枚举最大深度?因为由于枚举了所有子集,所以不会漏掉正解。(想象最优解所打通的那棵树,在状态转移的时候一定存在一个~~欧皇走位(大雾)~~的状态,每次更新都和最优解一模一样(最优解也是一层一层往下打通道路的),转移完毕之后就一定是最优解了,不存在漏解的情况。(好像说得不怎么清楚,不懂的可以留言问我哦
初始值: (从地上开通的第一个结点)
有一些细节,注释标记在代码里了
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 13;
const int INF = 0x3f3f3f3f;
int dis[MAXN][MAXN];
int w[MAXN][1 << MAXN], dp[MAXN][1 << MAXN];
int Log[1 << MAXN], Nxt[MAXN], P[MAXN], g[1 << MAXN], Tmp[1 << MAXN];
int main(){
freopen("in.txt", "r", stdin);
memset(dis, 0x3f, sizeof(dis));
int n, m; scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++){
int a, b, l;
scanf("%d%d%d", &a, &b, &l); a--, b--; //编号为0~n-1,方便状压
if(l < dis[a][b]) dis[a][b] = dis[b][a] = l;
}
for(int i = 0; i < n; i++) Log[1 << i] = i;
memset(dp, 0x3f, sizeof(dp));
for(int i = 0; i < n; i++) dp[0][1 << i] = 0;
for(int i = 0; i < n; i++){ //深度
for(int x = 0; x < (1 << n); x++){ //状态
int tot = 0;
for(int a = 0; a < n; a++){
if(!(x & (1 << a))){ //该点还未被开通
Nxt[tot] = 60000000; //这里不能设大了,否则会爆INT
//有两种解决方法:1.最大值小点 2.换成long long或者增加许多防止爆成负数的判断
P[tot] = 1 << a;
for(int j = x; j; j -= j&-j){ //提取所有1
int b = Log[j&-j];
Nxt[tot] = min(1ll * Nxt[tot], 1ll * dis[a][b] * (i + 1));
}
tot++;
}
}
for(int j = 1; j < (1 << tot); j++){
g[j] = g[j - (j&-j)] + Nxt[Log[j&-j]]; //更新g[j]时,知道g[j - (j&-j)]已经被更新了
//所以直接用g[j - (j&-j)]更新g[j]
Tmp[j] = Tmp[j - (j&-j)] | P[Log[j&-j]];
//此处的Tmp表示枚举集合为j时,实际在图中的集合
//因为是预处理出所有不在集合中的点,相当于一个映射
dp[i + 1][x | Tmp[j]] = min(dp[i + 1][x | Tmp[j]], dp[i][x] + g[j]);
}
}
}
int ans = INF;
for(int i = 0; i <= n; i++) ans = min(ans, dp[i][(1 << n) - 1]);
printf("%d", ans);
return 0;
}
以上。想了几天,主要是优化的细节太多了