首先呈现刘汝佳的高级代码
// UVa1354 Mobile Computing
// Rujia Liu
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
struct Tree {
double L, R; // distance from the root to the leftmost/rightmost point
Tree():L(0),R(0) {}
};
const int maxn = 6;
int n, vis[1<<maxn];
double r, w[maxn], sum[1<<maxn];
vector<Tree> tree[1<<maxn];
void dfs(int subset) {
if(vis[subset]) return;
vis[subset] = true;
bool have_children = false;
for(int left = (subset-1)⊂ left; left = (left-1)&subset) {
have_children = true;
int right = subset^left;
double d1 = sum[right] / sum[subset];
double d2 = sum[left] / sum[subset];
dfs(left); dfs(right);
for(int i = 0; i < tree[left].size(); i++)
for(int j = 0; j < tree[right].size(); j++) {
Tree t;
t.L = max(tree[left][i].L + d1, tree[right][j].L - d2);
t.R = max(tree[right][j].R + d2, tree[left][i].R - d1);
if(t.L + t.R < r) tree[subset].push_back(t);
}
}
if(!have_children) tree[subset].push_back(Tree());
}
int main() {
int T;
scanf("%d", &T);
while(T--) {
scanf("%lf%d", &r, &n);
for(int i = 0; i < n; i++) scanf("%lf", &w[i]);
for(int i = 0; i < (1<<n); i++) {
sum[i] = 0;
tree[i].clear();
for(int j = 0; j < n; j++)
if(i & (1<<j)) sum[i] += w[j];
}
int root = (1<<n)-1;
memset(vis, 0, sizeof(vis));
dfs(root);
double ans = -1;
for(int i = 0; i < tree[root].size(); i++)
ans = max(ans, tree[root][i].L + tree[root][i].R);
printf("%.10lf\n", ans);
}
return 0;
}
这就是他的自顶向下构造。
接着提一下高效枚举
设S表示一个01状态集,那么它的所有非空子集x可以通过以下代码枚举。
for (int x = S; x; x = (x-1)&S)
x = (x-1)&S实际上是把S中的0全部忽略,并不断减1的结果,比如S=1011,则x分别为:1011, 1010, 1001, 1000, 0011, 0010, 0001。忽略S中第二位的0其实就是111, 110, 101, 100, 011, 010, 001。
称S中的1所在位为有效位,0所在位为无效位,则x中的无效位必为0,有效位为0或1,比如S=1011,x=1001(有效位加下划线)。-1就是加上-1补码1111…,可以想成把无效位的1先加上去,比如x=1001变成1101,再加有效位的1。由于无效位加完肯定是1,会把有效位的进位“传递”下去,然后再位与S使得无效位变成0,实际就相当于有效位加上1111…,也就是有效位-1。
最后分析几个细节
- 54行两个循环算出了每个子集重量。
- 20、21行避免了重复枚举
- 27行用异或算出右子集
- 28、29行算出了天平杠杆距离
- 33-39行循环算出天平宽度集
- 63、64行算总宽度洁简了特判代码
总而言之,太巧妙了。
原文地址:https://www.cnblogs.com/autoint/p/9520690.html
时间: 2024-11-01 23:55:07