hdu 6444 - 最大子段和(单调队列)

题目链接:点击这里

 

解题思路:

根据k我们会得到一个循环周期,将在同一个循环节的点都归为同一类,因为归为同一类的点走过一个循环周期得到的快乐值时一样的。

很明显如果循环周期是负的那一定就不走一个循环了。那直接求最长不超过m的最大连续子段和就行了。

如果循环周期大于0,就有两种可能了(循环周期次数a=m/len(len为周期长度),b=m%n):

1.走完a圈后再加上不超过b的最大连续字段和

2.走完a-1圈后再加上不超过len的最大连续字段和,因为刚好走完一圈不一定会比走了一部分多,加上一圈反而会使最大值变少。

 

#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define lowbit(x) x&(-x)
const int mod = 1e9+7;
const int mx =  1e5+5;
typedef long long ll;
ll n,m,k,s;
bool vis[mx];
vector<ll>g[mx];
ll sum[mx];
ll a[mx];
ll q[mx];
ll l,r;
void del(ll k,ll m){
    while(l<r&&k-q[l]>m)l++;
}
void ins(ll k){
    while(l<r&&sum[q[r-1]]>=sum[k]) r--;
    q[r++] = k;
}
ll getans(ll i,ll k){
    int n = g[i].size();
    for(int j = 1; j <= n; j++)
        sum[j] = g[i][j-1],sum[j] += sum[j-1];
    for(int i = 1; i <= n; i++)
        sum[i+n] = sum[n]+sum[i];
    for(int i = 1; i <= n; i++)
        sum[i+2*n] = sum[2*n]+sum[i];
    l = r = 0;
    ll ans = 0;
    ins(0);
    for(int i = 1; i <= 3*n; i++){
        del(i,k);
        if(ans<sum[i]-sum[q[l]])
            ans = sum[i]-sum[q[l]];
        ins(i);
    }
    return ans;
}
int main(){
    int t,ca = 1;
    scanf("%d",&t);
    while(t--){
        scanf("%lld%lld%lld%lld",&n,&s,&m,&k);
        for(int i = 0; i < n; i++)
            g[i].clear();
        for(int i = 0; i < n; i++)
            scanf("%lld",&a[i]);
        int cnt = 0;
        memset(vis,0,sizeof(vis));
        for(int i = 0; i < n; i++){
            if(!vis[i]){
                int j = i;
                while(!vis[j]){
                    vis[j] = 1;
                    g[cnt].push_back(a[j]);
                    j = (j+k)%n;
                }
                cnt++;
            }
        }
        ll ans = 0;
        for(int i = 0; i < cnt; i++){
            int len = g[i].size();
            ll tmp = 0;
            for(int j = 0; j < len; j++)
                tmp += g[i][j];
            ans = max(ans,getans(i,m));
            if(tmp<0)
                continue;
            ll op = m%len;
            ll d = m/len;
            tmp = max(d-1,0ll)*tmp;
            if(d>=1)
                op += len;//把最后一圈加进去考虑走完一圈还是不走那个大
            if(op==0){
                ans = max(ans,tmp);
                continue;
            }
            ans = max(ans,tmp+getans(i,op));
        }
        printf("Case #%d: %lld\n",ca++,max(0ll,s-ans));
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值