JZOJ 3463 军训
题目大意:有N个人,每个人有Ai,Bi,要给这N个人分组,有以下要求:
1、每个人必须被分到某个组,每个组内的人的序号必须是连续的【第I个人序号为I】
2、每个组的Ai最大值的和必须小于limit
求:满足以上条件的分组时,每个组Bi的和 的最大值 最小。
题目很显然要DP
好的这就是做题的手稿
朴素的n * n * limit还是想的出来的嘛【如图】,就是太慢。
观察一下,由于要求连续分段,意味着B的和是连续的一段。若B的和固定了,以某个人结尾的组所能分到的最远的人也就确定了。
于是我们就二分答案,确定一个B的上限。
问题变为
F[I] 表示以 I 结尾的limit最小为多少 , 若最终的F[N] <= limit 就缩小二分的范围,反之扩大。
F[I]的递推式显然可得,后面是一串max自然而然就想到了单调队列。
对于后面加的max(a[j + 1] ~ a[i])【我们称之为del】我们可以看出随着j越小,del单调递增。也就是说del是随着j增大而递减的。维护一颗决策值的线段树,给相同del的决策区间加上del。单调队列维护del值,若要pop出队列就加上-del消除。还有一个细节就是f[j]加的是a[j + 1] ~ i ,详细看代码吧。
请无视注释
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
typedef long long LL;
const int N = 20010;
#define lch(x) (x << 1)
#define rch(x) ((x << 1) | 1)
struct Tree{
LL v,del;
}tree[N * 4];
int n,limit,Mid,a[N],b[N],sum[N],mxt,pos[N],q[N],tail,head;
LL f[N];
inline LL min(LL x , LL y) { return (x < y) ? x : y;}
/*void Change(int x , int L , int R , int p , LL v)
{
tree[x].v += v;
if (L == R) return;
int mid = L + R >> 1;
if (p <= mid) Change(lch(x) , L , mid , p , v);
else Change(rch(x) , mid + 1 , R , p , v);
}*/
void Add(int x , int L , int R , int l , int r , int del)
{
if (r < L || l > R || l > r) return;
if (l <= L && R <= r) {tree[x].del += del; return;}
int mid = L + R >> 1;
if (tree[x].del)
{
Add(lch(x) , L , mid , l , r , tree[x].del) , Add(rch(x) , mid + 1 , R , l , r , tree[x].del);
}
Add(lch(x) , L , mid , l , r , del) , Add(rch(x) , mid + 1 , R , l , r , del);
tree[x].v = min(tree[lch(x)].v + tree[lch(x)].del , tree[rch(x)].v + tree[rch(x)].del);
tree[x].del = 0;
}
LL Query(int x , int L , int R , int l , int r)
{
if (r < L || l > R || l > r) return (1 << 30);
//if (l <= L && R <= r) return tree[x].v + tree[x].del;
if (l <= L && R <= r) return tree[x].v + tree[x].del;
int mid = L + R >> 1;
if (tree[x].del)
{
Add(lch(x) , L , mid , l , r , tree[x].del) , Add(rch(x) , mid + 1 , R , l , r , tree[x].del);
tree[x].v = min(tree[lch(x)].v + tree[lch(x)].del , tree[rch(x)].v + tree[rch(x)].del);
tree[x].del = 0;
}
return min(Query(lch(x) , L , mid , l , r) , Query(rch(x) , mid + 1 , R , l , r));
}
bool Check(int mst)
{
if (mst < mxt) return 0;
head = 1 , tail = 0;
for (int l = 1 , r = 1 ; r <= n ; r ++)
{
while (l <= r && sum[r] - sum[l - 1] > mst) ++ l;
int flag = 0;
while (q[head] < l - 1 && head <= tail)
{
if (!flag) Add(1 , 0 , n , 0 , q[head] - 1 , - a[q[head]]) , flag = 1;
else Add(1 , 0 , n , q[head - 1] , q[head] - 1 , - a[q[head]]);
head ++;
}
while (a[q[tail]] <= a[r] && tail >= head)
{
if (tail == head) Add(1 , 0 , n , 0 , q[tail] - 1, - a[q[tail]]);
else Add(1 , 0 , n , q[tail - 1] , q[tail] - 1, - a[q[tail]]);
tail --;
}
q[++ tail] = r;
if (tail == head) Add(1 , 0 , n , 0 , q[tail] - 1 , a[r]);
else Add(1 , 0 , n , q[tail - 1] , q[tail] - 1 , a[r]);
f[r] = Query(1 , 0 , n , l - 1 , r - 1);
Add(1 , 0 , n , r , r , f[r]);
// int p = pos[r] , last = r - 1 , del = a[r];
// if (p == r - 1) f[r] = f[r - 1] + del , last = p ,del = max(del , a[p]) , p = pos[p];
/* f[r] = 1 << 30;
while (p >= l - 1)
{
f[r] = min(f[r] , Query(1 , 0 , n , p , last) + del);
del = max(del , a[p]);
last = p , p = pos[p];
}
f[r] = min(f[r] , Query(1 , 0 , n , l - 1 , last) + del);
Change(1 , 0 , n , r , f[r]);
/*for (int i = r - 1 ; i >= l - 1; i --)
f[r] = min(f[i] + del , f[r]) , del = max(del , a[i]);*/
}
return f[n] <= limit;
}
int main()
{
freopen("1.in" , "r" , stdin);freopen("1.out" , "w" , stdout);
scanf("%d%d" , &n , &limit);
for (int i = 1 ; i <= n ; i ++)
{
scanf("%d%d" , &a[i] , &b[i]);
sum[i] = sum[i - 1] + b[i] , mxt = max(mxt , b[i]);
/* if (a[i - 1] > a[i]) pos[i] = i - 1 ; else
{
pos[i] = pos[i - 1];
while (a[pos[i]] <= a[i] && pos[i]) pos[i] = pos[pos[i]];
}*/
}
pos[0] = -10;
int l = 0 , r = sum[n] , ans;
while (l <= r)
{
Mid = (l + r) >> 1;
memset(tree , 0 , sizeof tree);
if (Check(Mid)) r = Mid - 1 , ans = Mid ; else l = Mid + 1;
}
printf("%d\n" , ans);
return 0;
}