题意:求两段长度为k的子列的最大和,两段不能相交,输出两个子列的开始位置。
思路(1):DP的思想,枚举第二个子列的开始位置。维护这个位置前的长度为k的子列的最大值就够了。
复杂度:O(n)
代码:
#include <cstdio>
#include <cstring>
#define maxn 200005
using namespace std;
int n,m,k;
long long a[maxn],sum[maxn];
int main()
{
int i,j,s,st,e;
long long ma,t,tmp,prema;
scanf("%d%d",&n,&k);
sum[0]=0;
for(i=1;i<=n;i++) // sum存前n项和
{
scanf("%I64d",&a[i]);
sum[i]=sum[i-1]+a[i];
}
ma=-1;
prema=-1;
for(j=k+1;j<=n-k+1;j++)
{
t=sum[j+k-1]-sum[j-1]; // 枚举子列二
tmp=sum[j-1]-sum[j-k-1];
if(tmp>prema) // 维护子列一的最大值
{
s=j-k;
prema=tmp;
}
if(prema+t>ma)
{
st=s; // 注意这行必须有 不能直接输出s、e 因为最大值没有更新时s也可能在变
e=j;
ma=prema+t;
}
}
printf("%d %d\n",st,e);
return 0;
}
思路(2):RMQ,枚举子列一,查询子列二的最大值。(RMQ的查询可以达到O(1))
复杂度:RMQ初始化O(n*lg(n))+O(n)
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#define maxn 300005
using namespace std;
int n,m,mm,k,ans,t;
long long a[maxn],sum[maxn];
long long f[maxn][64];
long long maxx(long long x1,long long x2)
{
return x1<x2?x2:x1;
}
void init()
{
int i,j;
mm=n-k+1;
for(i=1;i<=mm;i++)
{
f[i][0]=sum[i];
}
for(j=1;(1<<j)<=mm;j++)
{
for(i=1;i+j-1<=mm;i++)
{
f[i][j]=maxx(f[i][j-1],f[i+(1<<(j-1))][j-1]); // 数组得开大一点 不然这里可能越界
}
}
}
long long query(int l,int r)
{
t=0;
while((1<<(t+1))<=r-l+1) t++;
return maxx(f[l][t],f[r-(1<<t)+1][t]);
}
int main()
{
int i,j,s,e;
long long ma,tmp;
scanf("%d%d",&n,&k);
sum[1]=0;
for(i=1;i<=n;i++)
{
scanf("%I64d",&a[i]);
if(i<=k) sum[1]+=a[i];
}
for(i=2;i<=n-k+1;i++) // sum[i]存 a[i]~a[i+k-1] 的和
{
sum[i]=sum[i-1]-a[i-1]+a[i+k-1];
}
init();
m=n-2*k+1;
ma=-1;
for(i=1;i<=m;i++) // 枚举第一段 查询第二段 扫描一遍可以得到s和ma
{
tmp=sum[i]+query(i+k,n-k+1);
if(tmp>ma)
{
s=i;
ma=tmp;
}
}
tmp=ma-sum[s];
for(i=s+k;i<=n-k+1;i++) // 根据s、ma就很好得到e了
{
if(sum[i]==tmp)
{
e=i;
break ;
}
}
printf("%d %d\n",s,e);
return 0;
}