P1295 [TJOI2011]书架

题目链接

点我跳转

题目大意

给出一个长度为 \(n\) 的序列 \(a\) ,请将 \(h\) 分成若干段,满足每段数字之和都不超过 \(m\) ,最小化每段的最大值之和。

解题思路

先考虑 \(30\) 分做法

定义 \(dp[i]\) 表示将前 \(i\) 个数分成若干段的最小代价

那么易推出 \(dp[i] = max(dp[i] , dp[j - 1] + max(a[j] \sim a[i]))\) ,最后答案为 \(dp[n]\)

考虑 $ 100$ 分做法

定义 \(l[i]\) 为左边第一个大于 \(a[i]\) 的数的位置 , \(l[i]\)可以用单调栈求出

定义 \(get(i)\) 为满足 \(a[get(i)] + ... + a[i] <= m\) 的临界位置 , \(get(i)\)可以用二分来求

那么 \(dp_i = min(min(dp_{get(i)} \sim dp_{l_i}) , min(dp_{l_i + 1} \sim dp_i) + a_i)\)

可以用线段树维护三个信息 \(ans , pre , lazy\)

其中 \(ans\) 表示 \(dp[j] , pre\) 表示 \(dp[j - 1] ,lazy\) 表示 \(max(a[j] \sim a[i])\),那么 \(ans = pre + lazy\)

现从 \(a1\) 开始遍历,每加入一个 \(ai\) 会有以下三步

1、\(l(i)+1\sim i\) 区间的 \(lazy\) 变为 \(a_i\)\(get_i \sim l_i\) 区间的 \(lazy\) 不变)

2、第 \(i\) 个位置的 \(ans = dp_{i - 1}\)

3、\(dp_i = min(ans_{get_i} \sim ans_i)\)

所以我们只要用线段树进行区间修改,单点修改,区间查询即可

AC_Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int INF (0x3f3f3f3f3fll);
const int N = 3e5 + 10;
struct Tree{
	int l , r ;
	int mi , ans , lazy;
}tree[N << 2];
void push_up(int rt)
{
	tree[rt].mi = min(tree[rt << 1].mi , tree[rt << 1 | 1].mi);
	tree[rt].ans = min(tree[rt << 1].ans , tree[rt << 1 | 1].ans);
}
void push_down(int rt)
{
	if(tree[rt].lazy == INF) return ;
	int x = tree[rt].lazy;
	tree[rt].lazy = INF;
	tree[rt << 1].ans = tree[rt << 1].mi + x;
	tree[rt << 1 | 1].ans = tree[rt << 1 | 1].mi + x;
	tree[rt << 1 | 1].lazy = x , tree[rt << 1].lazy = x;
}
void build(int l , int r , int rt , int *a)
{
	tree[rt].l = l , tree[rt].r = r;
	if(l == r)
	{
		tree[rt].mi = tree[rt].ans = a[l]; 
		tree[rt].lazy = INF;
		return ;
	}
	int mid = l + r >> 1;
	build(l , mid , rt << 1 , a);
	build(mid + 1 , r , rt << 1 | 1 , a);
	push_up(rt);
}
void update(int pos , int val, int rt)
{
	int l = tree[rt].l , r = tree[rt].r;
	if(l == r) 
	{
		tree[rt].mi = val;
		return ;
	}
	if(tree[rt].lazy != INF) push_down(rt);
	int mid = l + r >> 1;
	if(pos <= mid) update(pos , val , rt << 1);
	else update(pos , val , rt << 1 | 1);
	push_up(rt); 
}
void update_range(int L , int R , int rt , int val)
{
	if(tree[rt].r<L||tree[rt].l>R) return;
	int l = tree[rt].l , r = tree[rt].r;
	if(L <= l && r <= R)
	{
		tree[rt].lazy = val;
		tree[rt].ans = tree[rt].mi + val;
		return; 
	}
	if(tree[rt].lazy != INF) push_down(rt);
	int mid = l + r >> 1;
	if(L <= mid) update_range(L , R , rt << 1 , val);
	if(R > mid) update_range(L , R , rt << 1 | 1 , val); 
	push_up(rt);
}
int query_min(int L , int R , int rt)
{
	int l = tree[rt].l , r = tree[rt].r;
	if(L <= l && r <= R)
	{
		return tree[rt].ans;
	}
	if(tree[rt].lazy != INF) push_down(rt);
	int mid = l + r >> 1;
	int ans = INF;
	if(L <= mid) ans = min(ans , query_min(L , R , rt << 1));
	if(R > mid) ans = min(ans , query_min(L , R , rt << 1 | 1));
	return ans;
}
int a[N] , l[N] , dp[N] , sum[N] , n , m;
stack<int>sta;
int get(int i)
{
	int l = 0 , r = i , res = 0;
	while(l <= r)
	{
		int mid = l + r >> 1;
		if(sum[i] - sum[mid - 1] > m) l = mid + 1;
		else r = mid - 1 , res = mid; 
	}
	return max(1LL , res);
}
signed main()
{
	for(int i = 0 ; i <= N - 10 ; i ++) dp[i] = INF;
	cin >> n >> m;
	a[1] = 0 , dp[1] = 0;
	build(1 , n + 10 , 1 , dp) , update(1 , 0 , 1);
	for(int i = 2 ; i <= n + 1 ; i ++) cin >> a[i] , sum[i] = sum[i - 1] + a[i];
	for(int i = 1 ; i <= n + 1 ; i ++)
	{
		while(sta.size() && a[sta.top()] < a[i]) sta.pop();
		if(sta.size()) l[i] = sta.top();
		else l[i] = 0;
		sta.push(i);
	}
	for(int i = 2 ; i <= n + 1 ; i ++)
	{	
		update(i , dp[i - 1] , 1); 
		int x = get(i) , y = l[i];
		update_range(y + 1 , i , 1 , a[i]);
		dp[i] = query_min(x , i , 1);	
	}
	cout << dp[n + 1] << '\n';
	return 0;
}
posted @ 2020-10-23 01:04  GsjzTle  阅读(214)  评论(0编辑  收藏  举报