画圆的沙滩

亦简亦美

分割数组

编程之美2.18节。作者的代码很精练,可惜解释不太清晰。

虽然代码只有三个for语句,可是头两个for语句不可以随意置换。而且,第二个for语句必须是以递减的方式计算。

取i个元素求和,把这些和构成一个集合。我们只记录<=sum/2的和,则最多可以获得n/2个集合S(i)。在构造S(i)的时候,可以使用S(i-1)中的和加上一个未使用的元素v[k]来构造。每多一个新元素,这些集合就可以扩张一次,这两个for循环的含义也即在此。

如果使用set来维护这些集合,由于自动忽略了重复的元素,算法实际上并不会达到O(2^n)的复杂度,最多为O(n^2*sum),即解法三的复杂度。而且,由于使用的是set,也避免了讨论中所面对的问题。至于扩展问题,含有负数的数组,一个简单的策略是把所有数都做一个偏移,从而转化为初始问题。

下面的实现中,我有意使用了for_each来遍历一个set,是期望于STL中set iterator递增的实现可以达到O(1)。事实上,在公司的产品中我有碰见对set进行遍历的代码,其效率相当低下。而实际上,那里使用lower_bound和upper_bound就可以达到目的了。这可能难以置信,但是这样的一个简单改动可以使产品的速度有20%以上的提高。

int diff(const vector<int>& vec) {
int sum = 0;
for (size_t i = 0; i < vec.size(); ++i)
sum
+= vec[i];

vector
< set<int> > sets(vec.size()/2 + 1);
sets.front().insert(
0);
for (size_t k = 0; k < vec.size(); ++k) {
for (size_t i = min(vec.size()/2, k + 1); i > 0; --i) {
set<int>& cs = sets[i-1];

struct fnInsert {
set<int>& to_;
int v_, s_;
fnInsert(
set<int>& to, int v, int s): to_(to), v_(v), s_(s) {}
void operator()(int c) {
if (c + v_ <= s_) to_.insert(c + v_);
}
};

for_each(cs.begin(), cs.end(), fnInsert(sets[i], vec[k], sum
/2));
}
}

return sum - 2*(*sets.back().rbegin());
}

int main() {
int n;
while (cin>>n) {
vector
<int> vec;
while (n--) *back_inserter(vec) = *istream_iterator<int>(cin);
cout
<<diff(vec)<<'\n';
}
return 0;
}

posted on 2011-03-22 15:29  acmaru  阅读(290)  评论(0编辑  收藏  举报

导航