洛谷P3959 宝藏
去年NOIP第二毒瘤(并不)的题终于被我攻克了,接下来就只剩noip难度巅峰列队了。
首先说一下三种做法:随机化,状压DP和搜索。
前两种做法我都A了,搜索实在是毒瘤,写鬼啊。
有些带DFS的记忆化搜索版状压DP,我看着感觉有点问题......
就连随机化里面那个贪心我看起来也感觉是错的.....但还是A了。
所以考试的时候不要虚,大胆写随机化,随便混个几十分,说不定就A了。
20分算法:给定一棵树,枚举根即可。
1 #include <cstdio> 2 #include <algorithm> 3 4 const int N = 13, M = 1010, INF = 0x7f7f7f7f; 5 6 int G[N][N], n; 7 8 int DFS(int x, int k, int f) { 9 int ans = 0; 10 for(int i = 1; i <= n; i++) { 11 if(G[x][i] && i != f) { 12 ans += DFS(i, k + 1, x); 13 ans += G[x][i] * k; 14 } 15 } 16 return ans; 17 } 18 19 int main() { 20 int m; 21 scanf("%d%d", &n, &m); 22 for(int i = 1, x, y, z; i <= m; i++) { 23 scanf("%d%d%d", &x, &y, &z); 24 if(G[x][y]) { 25 z = std::min(z, G[x][y]); 26 } 27 G[x][y] = G[y][x] = z; 28 } 29 30 int ans = INF; 31 for(int i = 1; i <= n; i++) { 32 ans = std::min(ans, DFS(i, 1, 0)); 33 } 34 printf("%d", ans); 35 36 return 0; 37 }
40分算法:边权相等,据说可以BFS/DFS/prim,反正我搞不出来....
70分算法:直接枚举全排列,然后按照排列顺序依次加点,贪心即可。
这里有个问题:当前点的深度会对后面节点造成影响,所以贪心出来的不一定是这个顺序的最优解。
但是你有很多排列啊,说不定哪一次就搞出正解来了呢?
1 #include <cstdio> 2 #include <algorithm> 3 4 const int N = 13, M = 1010, INF = 0x7f7f7f7f; 5 6 int G[N][N], n, a[N], dis[N]; 7 8 int main() { 9 int m; 10 scanf("%d%d", &n, &m); 11 for(int i = 1, x, y, z; i <= m; i++) { 12 scanf("%d%d%d", &x, &y, &z); 13 if(G[x][y]) { 14 z = std::min(z, G[x][y]); 15 } 16 G[x][y] = G[y][x] = z; 17 } 18 19 for(int i = 1; i <= n; i++) { 20 a[i] = i; 21 } 22 int ans = INF; 23 do { 24 dis[a[1]] = 1; 25 int t_ans = 0; 26 for(int i = 2; i <= n; i++) { 27 bool f = 0; 28 int small = INF, pos; 29 for(int j = 1; j < i; j++) { 30 if(!G[a[i]][a[j]]) { 31 continue; 32 } 33 f = 1; 34 if(small > dis[a[j]] * G[a[i]][a[j]]) { 35 small = dis[a[j]] * G[a[i]][a[j]]; 36 pos = j; 37 } 38 } 39 if(!f) { 40 t_ans = INF; 41 goto flag; 42 } 43 t_ans += small; 44 dis[a[i]] = dis[a[pos]] + 1; 45 } 46 flag: 47 ans = std::min(ans, t_ans); 48 }while(std::next_permutation(a + 1, a + n + 1)); 49 50 printf("%d", ans); 51 return 0; 52 }
100分算法_随机化:
把70分算法改进一下,不枚举全排列,而是random_shuffle,说不定哪次就搞出正解来了呢?
然后就真A了......考场上还不是送分送到死啊,反向筛人。跑的还贼快...
1 #include <cstdio> 2 #include <algorithm> 3 4 const int N = 13, M = 1010, INF = 0x7f7f7f7f; 5 6 int G[N][N], n, a[N], dis[N]; 7 8 int main() { 9 int m; 10 scanf("%d%d", &n, &m); 11 for(int i = 1, x, y, z; i <= m; i++) { 12 scanf("%d%d%d", &x, &y, &z); 13 if(G[x][y]) { 14 z = std::min(z, G[x][y]); 15 } 16 G[x][y] = G[y][x] = z; 17 } 18 19 for(int i = 1; i <= n; i++) { 20 a[i] = i; 21 } 22 int ans = INF, T = 100000; 23 while(T--) { 24 std::random_shuffle(a + 1, a + n + 1); 25 dis[a[1]] = 1; 26 int t_ans = 0; 27 for(int i = 2; i <= n; i++) { 28 bool f = 0; 29 int small = INF, pos; 30 for(int j = 1; j < i; j++) { 31 if(!G[a[i]][a[j]]) { 32 continue; 33 } 34 f = 1; 35 if(small > dis[a[j]] * G[a[i]][a[j]]) { 36 small = dis[a[j]] * G[a[i]][a[j]]; 37 pos = j; 38 } 39 } 40 if(!f) { 41 t_ans = INF; 42 goto flag; 43 } 44 t_ans += small; 45 dis[a[i]] = dis[a[pos]] + 1; 46 } 47 flag: 48 ans = std::min(ans, t_ans); 49 } 50 51 printf("%d", ans); 52 return 0; 53 }
100分算法_状压DP:
好,这个才是这篇博客的主要目的。
我们发现这个深度很难搞啊...
然后又发现同一个深度的话,边权的加权是一定的。
然后我们考虑把边权搞到状态里去,那就是f[i][j][k]表示根为i,最大深度为j,已选节点状态是k的最小权值。
然后发现根那个维度其实可以不要,所以就是f[i][j]表示最大深度为i,目前状态为j的最小权值。
然后转移就是枚举j的子集k,计算出从k扩展成j的最小权值,乘上i即可。
然后发现上面那个计算k->j的最小权值,我们对于每个i都要做一次,所以可以预处理出来。
然后搞一搞一些奇怪的细节,就A了...
具体见代码。
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 5 typedef long long LL; 6 const int N = 12, M = 1010, INF = 0x7f7f7f7f; 7 8 int G[N + 1][N + 1], n; 9 LL f[N + 1][1 << N], val[1 << N][1 << N]; 10 11 inline void out(int x) { 12 for(int i = 0; i <= 3; i++) { 13 printf("%d", (x >> i) & 1); 14 } 15 printf(" "); 16 } 17 18 int main() { 19 int m; 20 scanf("%d%d", &n, &m); 21 for(int i = 1, x, y, z; i <= m; i++) { 22 scanf("%d%d%d", &x, &y, &z); 23 if(G[x][y]) { 24 z = std::min(z, G[x][y]); 25 } 26 G[x][y] = G[y][x] = z; 27 } 28 29 int lm = 1 << n; 30 memset(f, 0x3f, sizeof(f)); 31 memset(val, 0x3f, sizeof(val)); 32 for(int i = 0; i < n; i++) { 33 f[0][1 << i] = 0; 34 } 35 for(int i = 1; i < lm; i++) { 36 for(int j = (i - 1) & i; j > 0; j = (j - 1) & i) { // j -> i 37 int ans = 0; 38 for(int k = 0; k < n; k++) { 39 if(!(i & (1 << k)) || (j & (1 << k))) { 40 continue; 41 } 42 int small = INF; 43 for(int l = 0; l < n; l++) { // l -> k 44 if(!(j & (1 << l)) || !G[l + 1][k + 1]) { 45 continue; 46 } 47 small = std::min(small, G[l + 1][k + 1]); 48 } 49 if(small == INF) { 50 ans = INF; 51 break; 52 } 53 ans += small; 54 } 55 val[j][i] = ans; 56 } 57 } 58 59 for(int i = 1; i <= n; i++) { // deep 60 for(int j = 1; j < lm; j++) { // state 61 for(int k = (j - 1) & j; k > 0; k = (k - 1) & j) { // subset 62 f[i][j] = std::min(f[i][j], f[i - 1][k] + val[k][j] * i); 63 /*printf("%d ", i); 64 out(k); 65 out(j); 66 printf("%lld + %lld \n", f[i - 1][k], val[k][j] * i);*/ 67 } 68 } 69 } 70 LL ans = 1ll * INF * INF; 71 for(int i = 0; i <= n; i++) { 72 ans = std::min(ans, f[i][lm - 1]); 73 } 74 printf("%lld", ans); 75 return 0; 76 }
发现上面一列"然后"......语文水平有待提高啊......