链接:HDU6606 Distribution of books
题意:
将一个长度为n的序列a[1]、a[2]、… 、a[n], 要求取前k段(要求各段连续,但不可交叉,每段元素个数≥1),使得最大的那段和最小,并输出该和。
1 <= n <= 2*105
1 <= k <= n
-109 <= ai <= 109
分析:
先求下前缀 s u m sum sum,再二分得到答案,对于可能的答案x,利用dp进行验证。
d p [ i ] : dp[i]: dp[i]:表示 前 i i i个数 可以得到的 最多 符合条件 的 段数
d
p
[
i
]
=
m
a
x
{
d
p
[
j
]
}
+
1
          
(
0
≤
j
<
i
,
s
u
m
[
i
]
−
s
u
m
[
j
]
≤
x
)
dp[i]=max\{dp[j]\}+1\;\;\;\;\;(0\le j<i,sum[i]-sum[j]\le x)
dp[i]=max{dp[j]}+1(0≤j<i,sum[i]−sum[j]≤x)
若最后存在
d
p
[
i
]
≥
k
dp[i]\ge k
dp[i]≥k,说明
x
x
x是可能的答案,否则不可能是答案。
若 x x x满足该条件,那 > x \gt x >x的数肯定满足该条件,答案只有可能 ≤ x \le x ≤x,则令 R = M I D − 1 R=MID-1 R=MID−1,但要注意,最后一次二分得到的 M I D MID MID未必是答案,因为其小于上一个 x x x,但未必可行,所以答案应是 最后一次满足条件的 x x x;
若 x x x不满足该条件,答案肯定 > x \gt x >x,则令 L = M I D + 1 L=MID+1 L=MID+1。
注意: 因为要求的各分段之间是连续的,所以 d p dp dp的初值不能设为0,而应当 全设为 − ∞ -\infty −∞,然后令 d p [ 0 ] = 0 dp[0]=0 dp[0]=0,这样就可以保证连续性。
对于查找 m a x { d p [ j } max\{dp[j\} max{dp[j},可 将sum离散化后 用 权值线段树维护:
s u m [ i ] − s u m [ j ] ≤ x sum[i]-sum[j]\le x sum[i]−sum[j]≤x可转化为 s u m [ j ] ≥ s u m [ i ] − x sum[j]\ge sum[i]-x sum[j]≥sum[i]−x;
从而只需要找到离散化以后 s u m [ i ] − x sum[i]-x sum[i]−x的位置,然后线段树查询找到 d p [ j ] dp[j] dp[j],相应的树的初值要设为 − ∞ -\infty −∞,然后插入 s u m [ 0 ] = 0 sum[0]=0 sum[0]=0。
以下代码:
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=2e5+50;
int n,k,N;
LL a[maxn],sum[maxn],b[maxn],ans;
int t[maxn<<2];
void init()
{
sort(b+1,b+n+2);
N=unique(b+1,b+n+2)-(b+1); //离散化后的范围:1~N
for(int i=0;i<=n;i++)
sum[i]=lower_bound(b+1,b+N+1,sum[i])-b;
}
void build(int rt,int l,int r)
{
if(l==r)
{
t[rt]=-INF;
return;
}
int mid=(l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
t[rt]=max(t[rt<<1],t[rt<<1|1]);
}
void updata(int rt,int l,int r,int p,int val)
{
if(l==r)
{
t[rt]=max(t[rt],val);
return;
}
int mid=(l+r)>>1;
if(p<=mid)
updata(rt<<1,l,mid,p,val);
else
updata(rt<<1|1,mid+1,r,p,val);
t[rt]=max(t[rt<<1],t[rt<<1|1]);
}
int query(int rt,int l,int r,int ql,int qr)
{
if(ql<=l&&r<=qr)
return t[rt];
if(r<ql||l>qr)
return -INF;
int mid=(l+r)>>1;
return max(query(rt<<1,l,mid,ql,qr),query(rt<<1|1,mid+1,r,ql,qr));
}
bool check(LL x)
{
int dp[maxn];
build(1,1,N); //初始化树为-INF
updata(1,1,N,sum[0],0); //把sum[0]=0放入树中
for(int i=1;i<=n;i++)
{
int pos=lower_bound(b+1,b+N+1,b[sum[i]]-x)-b; //找到离散化后的位置pos
if(pos>N) //如果pos超过N,肯定找不到对应的dp[j],直接跳过
continue;
dp[i]=query(1,1,N,pos,N)+1;
updata(1,1,N,sum[i],dp[i]);
if(dp[i]>=k)
{
ans=min(ans,x);
return true;
}
}
return false;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d %d",&n,&k);
b[n+1]=sum[0]=0; //多加一个sum[0]=0
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
sum[i]=sum[i-1]+a[i];
b[i]=sum[i];
}
init(); //离散化
LL L=-1e15,R=1e15,MID;
ans=LLONG_MAX; //ans记得初始化
while(L<=R) //二分
{
MID=(L+R)>>1;
if(check(MID))
R=MID-1;
else
L=MID+1;
}
printf("%lld\n",ans);
}
return 0;
}