Problem
HYSBZ 开学了!今年HYSBZ 有n 个男生来上学,学号为1…n,每个学生都必须参加军训。在这种比较堕落的学校里,每个男生都会有Gi 个女朋友,而且每个人都会有一个欠扁值Hi。学校为了保证军训时教官不会因为学生们都是人生赢家或者是太欠扁而发生打架事故,所以要把学生们分班,并做出了如下要求:
1.分班必须按照学号顺序来,即不能在一个班上出现学号不连续的情况。
2.每个学生必须要被分到某个班上。
3.每个班的欠扁值定义为该班中欠扁值最高的那名同学的欠扁值。所有班的欠扁值之和不得超过Limit。
4.每个班的女友指数定义为该班中所有同学的女友数量之和。在满足条件1、2、3 的情况下,分班应使得女友指数最高的那个班的女友指数最小。
请你帮HYSBZ 的教务处完成分班工作,并输出女友指数最高的班级的女友指数。
输入数据保证题目有解。
Hint
对于20%的数据:n,Limit<=100
对于40%的数据:n<=1000
对于100%的数据:1<=n,Gi<=20000,1<=Hi,Limit<=10^7
Solution
算法1:二分+DP
- 这道题一眼
切DP。 - 囿于要维护欠扁值的sum以及女友指数的max,直接DP不太好搞。
- 考虑先二分一个答案maxG。
- 设f[i]表示做到第i个人,且将第i个人分到他们班的最后一个时,前i个人组成的所有班级的欠扁值和的最小值。
- 对于每个i,我们记录一个L,表示他往左扩展,最多能扩展到哪里,使得L~i能组成一个班级。换句话说,L是满足 ∑ij=LG[j]≤maxG ∑ j = L i G [ j ] ≤ m a x G 的所有数中的最小值。
- O(n3) O ( n 3 ) 的转移方程很显然: f[i]=minL≤j≤i{f[j−1]+maxj≤k≤i{H[k]}} f [ i ] = m i n L ≤ j ≤ i { f [ j − 1 ] + m a x j ≤ k ≤ i { H [ k ] } } 。
- 将括号内的那个 maxO(n2) m a x O ( n 2 ) 预处理一下,或在枚举j的时候顺便求一下(逆序枚举j),即可达到 O(n2) O ( n 2 ) 的转移复杂度。
- 时间复杂度: O(n2∗log2∑ni=1G[i]) O ( n 2 ∗ l o g 2 ∑ i = 1 n G [ i ] ) 。期望得分:40。
Code
#include <bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
#define MIN(x,y) x=min(x,y)
#define MAX(x,y) x=max(x,y)
using namespace std;
typedef long long ll;
const int N=20001;
int i,j,n,Limit,H[N],G[N],f[N],L,h;
ll sum,l,r,mid;
bool dp(ll maxG)
{
fo(i,1,n) f[i]=Limit+1;
sum=0; L=1;
fo(i,1,n)
{
sum+=G[i];
while( L<=i && sum>maxG ) sum-=G[L++];
h=0;
fd(j,i,L)
{
MAX(h,H[j]);
MIN(f[i],f[j-1]+h);
}
}
return f[n]<=Limit;
}
int main()
{
scanf("%d%d",&n,&Limit);
fo(i,1,n) scanf("%d%d",&H[i],&G[i]), sum+=1ll*G[i];
l=1; r=sum;
while(l<r)
{
mid=l+r>>1;
if(dp(mid))
r=mid;
else l=mid+1;
}
printf("%lld",l);
}
算法2:线段树优化
- 考虑使用线段树优化上述DP。
- 我们建三棵线段树minf、mins、maxp。
- 若minf有个区间只含一个位置,设为第j个位置,则它表示 minik=j{f[k]} m i n k = j i { f [ k ] } ;然后若含多个位置,则为左区间与右区间的min。
- mins则表示从j转移到i的答案(即minf+p);maxp则表示从j到i中的最大的欠扁值。
- 每次求答案前,我们都须用H[i]更新一下线段树。我们要将整棵线段树的maxp与H[i]取max。
- 仔细分析一下,其实并非如此。
- 设left[i]表示i左边的第一个比他更欠扁的人,则我们在从left[i]转移到i时(left[i]与i不在同一班),maxp应为H[i];而在left[i]左边,则maxp应≥H[left[i]]≥H[i]。
- 所以,我们直接让[left[i],i-1]这段区间的maxp区间赋值为H[i]即可。打个lazy_tag即可解决问题。
- 而如何顺便维护mins呢?试想一下,我们在将某个区间的maxp区间赋值的时候,p全部变成了那个值;那mins直接=minf+p了。
- 至于left数组,打个单调栈就行了。
- 然后,我们在求f[i]时,直接区间查询mins[L-1,i-1]即可。温习一下:L表示他往左扩展,最多能扩展到哪里,使得L~i能组成一个班级。
- 求解完毕后,直接将f[i]插进minf中i的位置即可。
- 时间复杂度: O(n∗log2n∗log2∑ni=1G[i]) O ( n ∗ l o g 2 n ∗ l o g 2 ∑ i = 1 n G [ i ] ) 。期望得分:100。
Code
#include <bits/stdc++.h>
#define A v<<1
#define B A|1
#define left asdoghdfhobdsf
#define fo(i,a,b) for(i=a;i<=b;i++)
#define clear(a) memset(a,0,sizeof a)
using namespace std;
typedef long long ll;
const int N=20001,S=N<<3;
int i,n,Lim,H[N],G[N],d[N],left[N],L,px,py;
ll sum,l,r,mid,f,minf[S],mins[S],maxp[S],pv;
inline void mark(int v,ll pv)
{
mins[v]=minf[v]+(maxp[v]=pv);
}
inline void push(int v)
{
ll pv=maxp[v];
if(!pv) return;
mark(A,pv); mark(B,pv);
maxp[v]=0;
}
ll query(int v,int l,int r)
{
if( py<l || r<px ) return Lim+1;
if( px<=l && r<=py ) return mins[v];
push(v);
int mid=l+r>>1;
return min(query(A,l,mid),query(B,mid+1,r));
}
void add(int v,int l,int r)
{
if(l==r) {minf[v]=f; return;}
int mid=l+r>>1;
i<=mid ? add(A,l,mid) : add(B,mid+1,r);
minf[v]=min(minf[A],minf[B]);
}
void modify(int v,int l,int r)
{
if( py<l || r<px ) return;
if( px<=l && r<=py ) {mark(v,pv); return;}
push(v);
int mid=l+r>>1;
modify(A,l,mid); modify(B,mid+1,r);
mins[v]=min(mins[A],mins[B]);
}
bool dp(ll maxG)
{
clear(minf); clear(mins); clear(maxp);
sum=0; L=1;
fo(i,1,n)
{
sum+=G[i];
while( L<i && sum>maxG ) sum-=G[L++];
px=left[i]; py=i-1; pv=H[i]; modify(1,0,n);
px=L-1, py=i-1, f=query(1,0,n);
add(1,0,n);
}
return f<=Lim;
}
int main()
{
scanf("%d%d",&n,&Lim);
fo(i,1,n)
{
scanf("%d%d",&H[i],&G[i]), sum+=1ll*G[i];
while( d[0] && H[i]>H[d[d[0]]] ) d[0]--;
left[i] = d[d[0]];
d[++d[0]]=i;
}
l=0,r=sum;
while(l<r)
{
mid=l+r>>1;
if(dp(mid))
r=mid;
else l=mid+1;
}
printf("%lld",l);
}