HDU6444(子段和、分情况比较)

要点

  • 不难想到gcd一下然后枚举每个开头走一圈,并记录一下数值。
  • 最终答案是分情况的:1.能走几圈走几圈然后加上最后剩余的最大子段和;2.也可能是最后一圈后面的拖后腿了,所以最后一圈没走完就停,即长度为一圈长的最大子段和;3.一圈为负数时只考虑一圈内的即可,多走了反而变差。
  • 求环形的、不超过某长度的最大子段和,用双端队列处理一下前缀和。
const int maxn = 1e4 + 5;
int T, n, m, k;
ll s, a[maxn];

ll calc(vector<ll> v, int length) {
    if (!length)    return 0LL;

    ll ret = -INF;
    vector<ll> sum;
    deque<int> Q;//单增的

    for (int i = 0; i < length; i++)    v.push_back(v[i]);
    sum.push_back(v[0]);
    for (int i = 1; i < v.size(); i++)  sum.push_back(sum.back() + v[i]);

    for (int i = 0; i < v.size(); i++) {
        while (Q.size() && Q.front() + length < i)  Q.pop_front();//保证不超过长度
        ll tmp = sum[i] - (Q.size() ? sum[Q.front()] : 0);//sum[I] - min{sum[j]}
        ret = max(ret, tmp);

        while (Q.size() && sum[Q.back()] >= sum[i]) Q.pop_back();//又靠前又大,不用活了
        Q.push_back(i);
    }
    return ret;
}

ll solve() {
    function<int(int, int)> gcd = [&](int a, int b) { return b ? gcd(b, a % b) : a; };
    int t = gcd(k, n), rest = m % (n / t);
    ll res = 0;

    rep(d, 0, t - 1) {
        vector<ll> v;
        ll sum = 0LL;

        for (int i = d, j = 1; j <= n / t; j++) {//走完一圈
            v.push_back(a[i]);
            sum += a[i];
            i = (i + k) % n;
            if (i == d) break;
        }

        ll tmp = calc(v, rest);//最后“半圈”的最大子段和
        if (m / (n / t) > 0) {//有能力走一圈以上时
            if (sum > 0)
                tmp = max(tmp + (ll)m / (n / t) * sum, (ll)(m / (n / t) - 1) * sum + calc(v, n / t));
            else    tmp = max(tmp, calc(v, n / t));
        }

        res = max(res, tmp);
    }

    return res;
}

int main() {
    read(T);
    for (int kase = 1; kase <= T; kase++) {
        read(n), read(s), read(m), read(k);
        rep(i, 0, n - 1)    read(a[i]);
        printf("Case #%d: %lld\n", kase, max(0LL, s - solve()));
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/AlphaWA/p/10739351.html
今日推荐