最近心情不好,而且今天已经wa到哭了,感觉特别悲催!感觉自己的这题的思路还不错,必须来写写博客释放下自己的情绪。
题目大意:有N个节点(0~N-1),给一个邻接矩阵,Cij代表节点i到节点j的花费,其中Cii == 0, Cij == Cji。需要把所有的节点分成A,B两部分,其中的花费就是 ∑Cij (i∈A,j∈B),求最大的花费。(2 <= N <= 20)
解题思路:上午做题的时候没有什么好的思路,但是也想到了怎样去枚举。就是直接用整数来表示集合,那么转换成2进制,如果第i位是0,就表示第i个节点在A集合,否则在B集合。我们来分析下时间复杂度,集合的个数最多是1 << 20,大概是10的6次方多一点,感觉这个时间怎么也不能再少了,然后对于每一个集合,我们的计算大概是N * N,那么这个时间复杂度怎么也通不过。但是,我们仔细思考,如果我们可以把 N * N降成N,那么这道题目大概就能AC了。那么要怎么降低这个时间复杂度呢?其实感觉有点像动态规划或者递推的思想,那就是我们可以保存一下前面的状态,由前面的已经求过的状态通过一个O(N)的复杂度直接推过来。那么问题就迎刃而解了。至于究竟是怎么推的,我已经在代码里面加好注释了,因为涉及到位操作,在这里可能讲不清楚。
Show me the code!
#include <cstdio>
using namespace std;
int N, cost[20][20], s[1 << 20], ans = 0;
int main() {
scanf("%d", &N);
for (int i = 0; i < N; ++i) {
for (int j = 0; j < N; ++j) {
scanf("%d", &cost[i][j]);
}
}
for (int i = 1; i < 1 << N; ++i) {
int t1 = 0, t2 = 0,
j = i - (i & -i);//j = i 去掉二进制中最低位的1
for (int k = 0; k < N; ++k) {//k 是 i 比 j 多的那一位所对应的节点
for (int l = 0; i - j >> k & 1 && l < N; ++l) {
//这里嵌套了两个for, 可是内层for只会执行一次, 所以是O(N)的
if (j >> l & 1) t1 += cost[k][l];
//t1 = 把第k个节点从一个集合移到另一个集合所减少的花费
else t2 += cost[k][l];
//t2 = 把第k个节点从一个集合转移到另一个集合所增加的花费
}
}
//s[i] <<==== s[j]
s[i] = s[j] - t1 + t2;
ans = ans > s[i] ? ans : s[i];
}
printf("%d\n", ans);
return 0;
}