HDU6444 Neko's loop(2018CCPC网络赛,线段树,思路)

版权声明:欢迎转载~转载请注明出处! https://blog.csdn.net/riba2534/article/details/82458505

Problem Description

Neko has a loop of size n.
The loop has a happy value ai on the i−th(0≤i≤n−1) grid.
Neko likes to jump on the loop.She can start at anywhere. If she stands at i−th grid, she will get ai happy value, and she can spend one unit energy to go to ((i+k)modn)−th grid. If she has already visited this grid, she can get happy value again. Neko can choose jump to next grid if she has energy or end at anywhere.
Neko has m unit energies and she wants to achieve at least s happy value.
How much happy value does she need at least before she jumps so that she can get at least s happy value? Please note that the happy value which neko has is a non-negative number initially, but it can become negative number when jumping.

Input

The first line contains only one integer T(T≤50), which indicates the number of test cases.
For each test case, the first line contains four integers n,s,m,k(1≤n≤104,1≤s≤1018,1≤m≤109,1≤k≤n).
The next line contains n integers, the i−th integer is ai−1(−109≤ai−1≤109)

Output

For each test case, output one line “Case #x: y”, where x is the case number (starting from 1) and y is the answer.

Sample Input

2
3 10 5 2
3 2 1
5 20 6 3
2 3 2 1 5

Sample Output

Case #1: 0
Case #2: 2

思路

首先说题意,有一个长度为 n 的循环序列,序列的每个位置有一个值,当你跳到这个位置,就会获得这个位置上的值的积分,你可以跳不超过 m 次,但是每次必须隔 k 个跳一下,现在你想要获得至少 s 积分,问你需要至少获得多少积分加上从这个循环序列里面得到的积分总和不小于 s ,实质上就是求出能在这个环上获得的最大值即可。

测试数据给出方式为,首先是 t 组测试数据,然后给出四个数 n , s , m , k ,然后接下来一行是这个 n 个数具体的值.

首先,对于一个环,每次只能走 k 步,一定存在循环节。由裴蜀定理我们可以得到,循环节的个数 c n t = g c d ( n , k ) ,循环节的长度为 l e n = n / c n t

我们的首要思路就是求出每个循环节上面的最大值。

那么对于每一个循环节,应该如何来求最大值?

对于每一个循环节,我们可以从这个循环节上的任意位置开始跳,跳的次数不超过 m 次,那么对于当前这个循环节所连成的环,我们可以求出一个前缀和sum[]数组出来,只要存在 s u m [ l e n ] > 0 那么就证明不论从循环节上的哪一个位置开始走,走完这一个循环节,我们所获得的权值一定是一个正值。

我们可以假设:

  • m >= l e n 的时候有两种情况:

    1. 此时走的步数大于循环节的长度,当 s u m [ l e n ] > 0 时,我们就尽量多的走这个循环节,可以获得的价值为sum[len]*(m/len),此时还剩下 m % l e n 的长度没有处理。我们接着处理剩下的长度,首先建立一棵线段树,维护一下sum[]数组的最小值,因为我们由前缀和可以计算出任意一个区间的和(sum[l...r]=sum[r]-sum[l-1]),我们已经知道当前区间的右端点sum[r],要使得和最大就要使得sum[l-1]的值尽量小,所以用线段树找一下长度为 m % l e n 的最大值。
    2. 第二种情况我们把从长度分成两个区间,第一个是[1...len],第二个是[len+1...m],和上面一样,我们求出[len+1...m]最多包含几个len,计算出结果为sum[len]*(M/len).接下来剩下的长度就是[1..len]了,我们利用线段树找出这个的最大值。然后两种情况求比较大的一个
  • m < l e n 时:

    这种情况就是我们在 m >= l e n 的第一种情况,计算 m % l e n 的时候已经包含进去了,不需要再计算。

代码

#include <bits/stdc++.h>
using namespace std;
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
#define mem(a, b) memset(a, b, sizeof(a))
typedef long long ll;
const ll N = 2e5 + 10;
const ll inf = 1e18;
ll n, m, s, k, a[N], b[N], sum[N], cnt, len, MIN[N << 2];
void pushup(ll rt)
{
    MIN[rt] = min(MIN[rt << 1], MIN[rt << 1 | 1]);
}
void build(ll l, ll r, ll rt)
{
    if (l == r)
    {
        MIN[rt] = sum[l];
        return;
    }
    ll m = (l + r) >> 1;
    build(lson);
    build(rson);
    pushup(rt);
}
ll query(ll L, ll R, ll l, ll r, ll rt)
{
    if (L <= l && r <= R)
        return MIN[rt];
    ll m = (l + r) >> 1;
    ll ans = inf;
    if (L <= m)
        ans = min(ans, query(L, R, lson));
    if (R > m)
        ans = min(ans, query(L, R, rson));
    return ans;
}
ll get_max(ll pos)
{
    ll ans = 0, mm = m;
    for (ll i = 1; i <= len; i++)
    {
        b[i] = b[i + len] = a[pos];
        pos = (pos + k) % n;
    }
    for (ll i = 1; i <= 2 * len; i++)
        sum[i] = sum[i - 1] + b[i];
    build(1, 2 * len, 1);
    ll res1 = 0, res2 = 0;
    if (sum[len] > 0)
        res1 = sum[len] * (mm / len);
    mm %= len;
    for (ll i = len + 1; i <= 2 * len; i++)
        res2 = max(res2, sum[i] - query(i - mm, i, 1, 2 * len, 1));
    ans = res1 + res2;
    if (m > len)
    {
        mm = (m - len);
        if (sum[len] > 0)
            res1 = sum[len] * (mm / len);
        mm = len;
        for (ll i = len + 1; i <= 2 * len; i++)
            res2 = max(res2, sum[i] - query(i - mm, i, 1, 2 * len, 1));
        ans = max(ans, res1 + res2);
    }
    return ans;
}
void solve()
{
    scanf("%lld%lld%lld%lld", &n, &s, &m, &k);
    for (ll i = 0; i < n; i++)
        scanf("%lld", &a[i]);
    cnt = __gcd(n, k), len = n / cnt;
    ll ans = 0;
    for (ll i = 0; i < cnt; i++)
        ans = max(ans, get_max(i));
    ans = ans > s ? 0 : s - ans;
    printf("%lld\n", ans);
}
int main()
{
    //freopen("in2.txt", "r", stdin);
    ll t, q = 1;
    scanf("%lld", &t);
    while (t--)
    {
        printf("Case #%lld: ", q++);
        solve();
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/riba2534/article/details/82458505