洛谷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 }
20分代码

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 }
AC代码

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 }
AC代码

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 }
AC代码

发现上面一列"然后"......语文水平有待提高啊......

posted @ 2018-09-30 11:55  garage  阅读(190)  评论(0编辑  收藏  举报