HDU 6444 Neko's loop(线段树+裴蜀定理/最长子段和)

探讨了Neko's Loop问题的解决方案,利用线段树和裴蜀定理来寻找环形路径上的最大幸福值,确保在有限步数内达到指定的幸福值标准。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接

Neko's loop

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 1557    Accepted Submission(s): 362


 

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个节点的环,每一个点有一个权值,你到那个点,你就可以获得那个点的权值,同一个点可以获得多次

你选择任意一个起点开始,每一步,你要么选择停止结束,要么走到(i+k)%n这个点

你最多只能走m步,给你一个标准s,问你如果你最后结束的获得的权值要>=s,问你开始之前最少需要

预先得到多少权值?答案是一个非负整数,但走路的时候,你获得的权值可能会变成负的

解析:

线段树+裴蜀定理

遍历数组时,线段树维护的是右端点是i的区间的最大值

maxx:表示1-n权值最大的区间

maxxs:表示1-n内,长度小于等于ss的最大权值的区间

这个题目,走的时候分情况讨论一下就可以了

sum<0时,走的路,一定不会超过一次循环

sum>0时,走到路,要么在m/n次循环中停下,要么在m/n+(m%n?1:0)次循环中停下

比较一下这两种情况,你获得权值哪个打,你就在哪里停下就好了。

这里数字要扩成2倍,因为是环

还有这里因为走的路线是跟gcd(n,k)有关的,如果gcd(n,k)=1,那么就是长度为n的循环,所有点都能被

走到,只有一种情况与起点无关,但是如果gcd(n,k)>1,那么就是长度为n/gcd(n,k)的循环,有gcd(n,k)种情况

与起点有关

那么你对于每一种情况都做一遍就可以了,因为每一种情况不可能有重复的点,所以最后的复杂度还是O(nlogn)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define Max(a,b) (a>=b?a:b)
#define Min(a,b) (a<=b?a:b)
using namespace std;
typedef long long ll;
const int MAXN = 2e4+100;

ll b[MAXN],a[MAXN];

#define lch (root<<1)
#define rch ((root<<1)|1)

typedef struct node
{
	//int y;
	ll minn;
	int pos;
}node;



node Btree[MAXN*5];
ll mark[MAXN*4];

inline void push_up(int root)
{
    Btree[root].minn=Max(Btree[lch].minn,Btree[rch].minn);
}

void build(int l,int r,int root)  //l,r表示他们在stu中的下标,root表示他们在线段树中的坐标
{
	if(l>r)return;
	if(l==r)
	{
		Btree[root].minn=0;
		mark[root]=0;
		return ;
	}
	int mid=(l+r)>>1;
	mark[root]=0;
	build(l,mid,root<<1);
	build(mid+1,r,(root<<1)|1);
    push_up(root);

}



inline void pushDown(int root)
{
	if(mark[root]!=0)
	{
		mark[lch]+=mark[root];
		mark[rch]+=mark[root];

		Btree[lch].minn+=mark[root];
		Btree[rch].minn+=mark[root];
		mark[root]=0;
	}

}



void update(int root,int s1,int e1,int s2,int e2,ll val)   //s1,e1表示当前区间,s2,e2表示目标区间
{
	if(e1<s2||s1>e2)
		return;
	if(s1>e1)return;
	if(s2<=s1&&e1<=e2)
	{
		Btree[root].minn+=val;
		mark[root]+=val;
		return;
	}
	pushDown(root);
	int mid=(s1+e1)>>1;
	if(s2<=mid)
		update(root<<1,s1,mid,s2,e2,val);
	if(mid+1<=e2)
		update((root<<1)|1,mid+1,e1,s2,e2,val);
	push_up(root);
}

ll query(int root,int s1,int e1,int s2,int e2)
{
	if(e1<s2||s1>e2)
		return 0;
	if(s1>e1)return 0;

	if(s1>=s2&&e1<=e2)
	{
		return Btree[root].minn;
	}

	pushDown(root);
	int mid=(s1+e1)>>1;
	ll ansl,ansr;
	ansl=ansr=0;
	if(s2<=mid)ansl=query(root<<1,s1,mid,s2,e2);
	if(mid+1<=e2) ansr=query((root<<1)|1,mid+1,e1,s2,e2);
	return Max(ansl,ansr);
}
int m,k;
ll s;
ll solve(int e,int n,int tot)
{
        ll sum=0;
        for(int i=1;i<=n;i++)
        {
            a[i]=b[e];
            e=(e+k)%tot;
            sum+=a[i];
        }
        for(int i=n+1;i<=2*n;i++)
        {
            a[i]=a[i-n];
        }
		build(1,n*2,1);
		ll kk=m/n;
		ll ss=m%n;
        ll maxx,maxxs;
        maxx=maxxs=0;
        int tmp,tmpx;
        int nn=n;
        n=n<<1;
		for(int i=1;i<=n;i++)
        {
            tmpx=Max(i-nn+1,1);
            update(1,1,n,tmpx,i,a[i]);
            maxx=Max(maxx,query(1,1,n,tmpx,i));
            if(ss)
            {
                tmp=Max(i-ss+1,1);
                maxxs=Max(maxxs,query(1,1,n,tmp,i));
            }
        }

        if(sum<0)
        {
            //printf("%lld\n",kk>0?Max(s-maxx,0):Max(s-maxxs,0));
            return kk>0?Max(s-maxx,0):Max(s-maxxs,0);
        }
        else
        {
            if(kk==0) //printf("%lld\n",Max(s-maxxs,0));
                return Max(s-maxxs,0);
            else
            {
                if(maxx>maxxs+sum) //printf("%lld\n",Max(0,s-((kk-1)*sum+maxx)));
                    return Max(0,s-((kk-1)*sum+maxx));
                else //printf("%lld\n",Max(0,s-((kk)*sum+maxxs)));
                    return Max(0,s-((kk)*sum+maxxs));
            }
        }
}

int gcd(int a,int b)
{
    int ans;
    while(b)
    {
        ans=a%b;
        a=b;
        b=ans;
    }
    return a;

}

int main()
{
    int n;
    int t;
	scanf("%d",&t);
	int cas=0;
	while (t--)
	{
	    cas++;
        scanf("%d%lld%d%d",&n,&s,&m,&k);
		for(int i=0;i<n;i++) scanf("%lld",&b[i]);
        int e=0;
        printf("Case #%d: ",cas);
        ll ans=s;
        int w=gcd(n,k);
        if(w!=1)
        {
            for(int i=0;i<w;i++)
            {
                ans=Min(ans,solve(e+i,n/w,n));
            }
        }
        else
        {
            ans=solve(0,n,n);
        }
        printf("%lld\n",ans);
	}

	return 0;
}

看了题解发现,可以直接用O(n)的方法求最长子段和...用了之后立即93ms。。。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define Max(a,b) (a>=b?a:b)
#define Min(a,b) (a<=b?a:b)
#define inf (0x3f3f3f3f3f3f3f3f)
using namespace std;
typedef long long ll;
const int MAXN = 2e4+100;

ll b[MAXN],a[MAXN];
ll Q[MAXN];
ll qsum[MAXN];

#define lch (root<<1)
#define rch ((root<<1)|1)

typedef struct node
{
    //int y;
    ll minn;
    int pos;
}node;


ll solve(ll num[], ll sum[], int len, int k, ll &ansl, ll &ansr) {//最大子段和模板 
    ll ans = -inf;
    int front = 0, rear = 0;
    for (int i = 1; i <= len+k; i++) {
        while (front < rear && sum[i-1] < sum[Q[rear-1]]) rear--;
        Q[rear++] = i-1;
        while (front < rear && i-Q[front] > k) front++;
        if (sum[i] - sum[Q[front]] > ans) {
            ans = sum[i] - sum[Q[front]];
            ansl = Q[front]+1;
            ansr = i;
        }
    }
    return ans;
}

int m,k;
ll s;
ll solve(int e,int n,int tot)
{
        ll sum=0;
		qsum[0]=0;
        for(int i=1;i<=n;i++)
        {
            a[i]=b[e];
            e=(e+k)%tot;
            sum+=a[i];
			qsum[i]=qsum[i-1]+a[i];
        }
        for(int i=n+1;i<=2*n;i++)
        {
            a[i]=a[i-n];
			qsum[i]=qsum[i-1]+a[i];
        }
        ll kk=m/n;
        ll ss=m%n;
        ll maxx,maxxs;
        maxx=maxxs=0;
        int tmp,tmpx;
        int nn=n;
		ll tmp1,tmp2;
		maxx=solve(a,qsum,n,n,tmp1,tmp2);
		maxxs=solve(a,qsum,n,ss,tmp1,tmp2);
        if(sum<0)
        {
            //printf("%lld\n",kk>0?Max(s-maxx,0):Max(s-maxxs,0));
            return kk>0?Max(s-maxx,0):Max(s-maxxs,0);
        }
        else
        {
            if(kk==0) //printf("%lld\n",Max(s-maxxs,0));
                return Max(s-maxxs,0);
            else
            {
                if(maxx>maxxs+sum) //printf("%lld\n",Max(0,s-((kk-1)*sum+maxx)));
                    return Max(0,s-((kk-1)*sum+maxx));
                else //printf("%lld\n",Max(0,s-((kk)*sum+maxxs)));
                    return Max(0,s-((kk)*sum+maxxs));
            }
        }
}

int gcd(int a,int b)
{
    int ans;
    while(b)
    {
        ans=a%b;
        a=b;
        b=ans;
    }
    return a;

}

int main()
{
    int n;
    int t;
    scanf("%d",&t);
    int cas=0;
    while (t--)
    {
        cas++;
        scanf("%d%lld%d%d",&n,&s,&m,&k);
        for(int i=0;i<n;i++) scanf("%lld",&b[i]);
        int e=0;
        printf("Case #%d: ",cas);
        ll ans=s;
        int w=gcd(n,k);
        if(w!=1)
        {
            for(int i=0;i<w;i++)
            {
                ans=Min(ans,solve(e+i,n/w,n));
            }
        }
        else
        {
            ans=solve(0,n,n);
        }
        printf("%lld\n",ans);
    }

    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值