0
点赞
收藏
分享

微信扫一扫

ZR #1190. 【线上训练 15】生成树


题目链接:​​传送门​​(没买的看不了)

小的一批,状压钦定
表示点的集合且根节点为的最小值,当然一定在
转移的时候枚举的一个子集内的节点
如果将作为的根节点并且和结合起来
那么可由转移过来

表示的边权,的个数
他们俩相乘就是这条边对这个生成树权值和的贡献
这样转移复杂度是的,可以过
这里的是乘上了枚举子集和子集的补集的复杂度所以比较奇怪
继续优化一下让数组中也存着补集的最优情况,也就是也包含了补集的答案,这样可以少枚举一层补集,优化掉了一层
经过这样处理之后复杂度变成了,可以通过
下面代码既有80的也有优化后的100的

#include <bits/stdc++.h>
#define

using namespace std;
typedef long long ll;
int n; ll g[A][A], siz[1 << A];
ll coms[1 << A][A], sizt[1 << A];
ll f[1 << A][A], w[A];

int main(int argc, char const *argv[]) {
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++)
scanf("%d", &g[i][j]), g[j][i] = g[i][j];
for (int i = 1; i < 1 << n; i++)
for (int j = 1; j <= n; j++)
if (!(i & (1 << (j - 1)))) coms[i][++sizt[i]] = j; //i集合的补集; sizt[i]是补集大小
for (int i = 1; i <= n; i++) w[i] = 1LL * i * (n - i); //w[i]是二进制下有i个1时任意一条边的贡献次数,与点的个数有关
memset(f, 0x3f, sizeof f);
for (int i = 1; i <= n; i++) f[1 << (i - 1)][i] = 0;
for (int i = 1; i < 1 << n; i++) siz[i] = siz[i >> 1] + (i & 1); //i有多少位1
for (int s = 1; s < 1 << n; s++) //当前点的集合
for (int i = 1; i <= n; i++)
if (s & (1 << (i - 1))) { //这个点是否在集合内
int s1 = s ^ (1 << (i - 1)); //枚举s的真子集
for (int t = s1 & (s1 - 1); t; t = (t - 1) & s1) //让f也包括补集的最优情况以减少一层补集的枚举
f[s][i] = min(f[s][i], f[(s1 ^ t) | (1 << (i - 1))][i] + f[t | (1 << (i - 1))][i]);
for (int j = 1; j <= n; j++) if (!(s & (1 << (j - 1)))) //可转移到其他点
f[s | (1 << (j - 1))][j] = min(f[s | (1 << (j - 1))][j], f[s][i] + g[i][j] * w[siz[s]]);
// for (int s1 = (s - 1) & s; s1; s1 = (s1 - 1) & s) //枚举s的子集s1
// if ((s1 & (1 << i - 1))) //当前点也在s的子集s1中
// for (int j = 1; j <= sizt[s1]; j++) { //枚举不在s1中的点(枚举s1补集中的点)
// int t = siz[s ^ s1], l = coms[s1][j]; //s^s1就是s1对s的补集,l是该点
// f[s][i] = min(f[s][i], f[s ^ s1][l] + f[s1][i] + g[i][l] * w[t]);
// }
}
ll ans = 1e18;
for (int i = 1; i <= n; i++) ans = min(ans, f[(1 << n) - 1][i]);
cout << ans << endl;
}


举报

相关推荐

0 条评论