您的位置:首页 > 其它

codeforces895A Pizza Seperation

2017-12-14 22:34 134 查看
Div2的T1出这个真的好吗…

思考余地很大的一道题。

来说说题意吧。有一个和为360的环形数列,将其分成两半使得差最小。

一个直观的做法是滚动n次,然后我们可以预处理一个前缀和,并从前往后扫一遍,这样可以过掉这道题,复杂度是O(n2)的。但这个复杂度显然不能让我们满意。

于是作者给出了一个很有趣的做法:

首先明确一点,对于一个环形数列来说,将之展开之后,分成两部分,一定有一部分是处于当前的展开之内的。

比如说:

170 30 150 10

这样最小的差出现在情况170+10和30+150上,在该情况下,尽管170和10是分开的,但是30和150仍然是连续的一段。

由于如果一段区间是确定的,比如t,那么另外部分的和就是360−t,所以两部分的差值就是|2t−360|,换言之,180就是我们期望达到的该区间和,因为此时差值为0。

所以可以以此为基础来设计贪心策略:

移动区间的右端点,每次移动求出最值

当区间和>=180时,移动左端点直到再次<180,当然每次移动也要求出最值

有些类似滑动窗口的做法,由于每个元素最多有1次进入左端点和右端点,所以复杂度是O(n)的。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
int n, a[370], sum, ans = 0x3fffff;
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin>>n;
for(int i = 1; i <= n; ++i) cin>>a[i];
int l = 1 , r = 1;
while(r <= n) {
//cout<<"r"<<r<<endl;
sum += a[r];
while(sum >= 180) {
//cout<<"l"<<l<<" ";
ans = min(ans, abs(360 - 2 * sum));
sum -= a[l];
l++;
}
ans = min(ans, abs(360 - 2 * sum));
r++;
}
cout<<ans<<endl;
}


之所以这道题可玩性很强,是因为稍微改一改条件,就可以有很多值得思考之处。

我们得到一个结论:对于和为n的环形数组分两段,以n>>1为基准贪心是一种明智的选择。

如果不是必须连续,分成两个集合,又如何呢?

然后我在查资料的时候,看到了一个很有趣的博客:

http://blog.csdn.net/htq__/article/details/50925205

其思路给了我一个提示:由于我们想要达到n>>1这个值,其实这个是等价于0-1背包的,也就是从n个元素中选取n个和不超过n>>1并且和最大的,也就是dp[i]=max(dp[i],dp[i−a[i]])+a[i]

还有可以展开思考的余地。如果是分成k段区间使得和的方差最小,又有什么做法呢?……
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息