codeforces895A Pizza Seperation

本文探讨了一道关于环形数列的算法题,目标是将数列分成两半使和的差值最小。介绍了直观的O(n²)解法及更高效的O(n)贪心算法,并提出基于此题目的拓展思考。

Div2的T1出这个真的好吗…
思考余地很大的一道题。


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

一个直观的做法是滚动n次,然后我们可以预处理一个前缀和,并从前往后扫一遍,这样可以过掉这道题,复杂度是 O(n2) 的。但这个复杂度显然不能让我们满意。
于是作者给出了一个很有趣的做法:
首先明确一点,对于一个环形数列来说,将之展开之后,分成两部分,一定有一部分是处于当前的展开之内的。
比如说:
170 30 150 10
这样最小的差出现在情况170+10和30+150上,在该情况下,尽管170和10是分开的,但是30和150仍然是连续的一段。
由于如果一段区间是确定的,比如 t ,那么另外部分的和就是360t,所以两部分的差值就是 |2t360| ,换言之, 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.youkuaiyun.com/htq__/article/details/50925205
其思路给了我一个提示:由于我们想要达到 n>>1 这个值,其实这个是等价于0-1背包的,也就是从n个元素中选取n个和不超过 n>>1 并且和最大的,也就是 dp[i]=max(dp[i],dp[ia[i]])+a[i]


还有可以展开思考的余地。如果是分成k段区间使得和的方差最小,又有什么做法呢?……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值