zoukankan      html  css  js  c++  java
  • hdu 6444 网络赛 Neko's loop(单调队列 + 裴蜀定理)题解

    题意:有编号为0~n-1的n个游戏,每个活动都有一个价值(可为负),给你m,s和k,你可以从任意一个编号开始玩,但是下一个游戏必须是编号为(i + k)%n的游戏,你最多能玩m次游戏,问你如果最后你手里要有s的价值,那么你至少一开始要有多少价值。

    思路:由裴蜀定理可以知道,如果有n个值首尾相连,间隔为k地走,那么最后会有一个循环节,这样的循环节一共有gcd(n, k)个,每个循环节长度n / gcd(n, k)个。所以我们只要找出所有循环节,并且把每个循环节的最大价值算出来就行了。对于每个循环节,他能获得的最大价值有两种情况,记循环节长度len:

    1.m整除len:找长度不超过len的能获得的最大价值,如果走一遍循环节获得价值为正 ,那么再加上sum*(m / len - 1);为负肯定不走好几圈,越走越小的

    2.m不整除len:找长度不超过m%len的最大价值,如果走一遍循环节获得价值为正 ,那么再加上sum*(m / len);这里有一种情况可能前面提到的状况不是最优,当走一遍循环节为负并且m>len时,问题其实转化为了上面第一种情况

    故当次循环节的最大价值为以上两种情况最大值。

    关于求“不超过len的最大价值”和“不超过m%len的最大价值”可以用单调队列解决:开两倍循环节长度的数组储存两个循环节,然后变成前缀和,这样我们可以用单调队列维护,用前缀和相减计算出最大价值。

    参考:HDU 6444 Neko's loop(思维+长度不超过L的最大子段和)

    代码:

    #include<map>
    #include<queue>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define ll long long
    using namespace std;
    const int maxn = 20000 + 10;
    const int seed = 131;
    const int MOD = 1000000000 + 7;
    const int INF = 0x3f3f3f3f;
    ll a[maxn];
    ll arr[maxn], q[maxn];
    int vis[maxn];
    int gcd(int a, int b){
        return b == 0? a : gcd(b,a % b);
    }
    ll sum[maxn];   //前缀和
    ll cal(int sz, int len){
        ll ret = 0;
        ll head = 0, tail = 0;
        for(int i = 1; i <= len << 1; i++){
            if(i == 1) sum[i] = arr[i];
            else sum[i] = sum[i - 1] + arr[i];
            if(i <= sz) ret = max(ret, sum[i]);
            while(head < tail && q[head] + sz < i) head++;
            if(head < tail) ret = max(ret, sum[i] - sum[q[head]]);
            while(head < tail && sum[q[tail - 1]] >= sum[i]) tail--;
            q[tail++] = i;
        }
        return ret;
    }
    ll solve(int m, int len){
        ll all = 0;
        for(int i = 1; i <= len; i++)
            all += arr[i];  //总和
        ll ans1 = cal(m % len, len);
        ll ans2 = cal(len, len);
        if(all > 0 && m / len >= 1)
            ans1 += all * 1LL * (m / len);
        if(all > 0 && m / len >= 2)
            ans2 += all * 1LL * (m / len - 1);
        return max(ans1, ans2);
    }
    int main(){
        int T, Case = 1;
        ll n, m, k, s;
        scanf("%d", &T);
        while(T--){
            scanf("%lld%lld%lld%lld", &n, &s, &m, &k);
            for(int i = 0; i < n; i++){
                scanf("%lld", &a[i]);
                vis[i] = 0;
            }
            int num = gcd(n, k);    //循环节个数
            int len= n / num;      //循环节长度
            ll Max = -INF;
            for(int i = 0, tt = 1; tt <= num && i < n; i++){
                if(vis[i]) continue;
                vis[i] = 1;
                int point = i;
                for(int j = 1; j <= len; j++){
                    arr[j] = arr[j + len] = a[point];
                    point = (point + k) % n;
                    vis[point] = 1;
                }
                Max = max(Max, solve(m, len));
                tt++;
            }
            if(Max >= s)
                printf("Case #%d: 0
    ", Case++);
            else
                printf("Case #%d: %lld
    ", Case++, s - Max);
        }
        return 0;
    }
  • 相关阅读:
    redis
    装饰器之functools与before_request
    版本
    git常用命令
    支付宝支付示例
    ContentType
    vue的基础使用
    es6简单介绍
    解析器、路由控制、分页与响应器
    元素水平居中的方法
  • 原文地址:https://www.cnblogs.com/KirinSB/p/9551631.html
Copyright © 2011-2022 走看看