Nutella’s Life-斜率优化+线段树

如有疏漏错误之处,请多指教

题意

codeforce.com发布了未来一年的比赛列表。未来一年将会有 n ( 1 ≤ n ≤ 1 0 5 ) n(1\leq n \leq 10^{5}) n(1n105)场比赛。小红为每场比赛计算了一个快乐值 a [ i ] ( 1 0 − 9 ≤ a [ i ] ≤ 1 0 9 ) a[i](10^{-9} \leq a[i] \leq 10^{9}) a[i](109a[i]109)。小红参加比赛的规则和快乐值获取规则如下:

  1. 如果小红参与了比赛 i , j ( i < j ) i,j(i i,j(i<j),应有 a [ i ] ≤ a [ j ] a[i] \leq a[j] a[i]a[j]
  2. 如果小红参与了比赛 i i i,她将获得 a [ i ] a[i] a[i]的快乐值
  3. 如果小红没有参与比赛 i i i,并且这是连续 k k k场没有参与的比赛,她将失去k个快乐值

问小红能获取的最大快乐值是多少?

standard input standard output
7
1 3 2 7 3 2 4
7
7
-3 -4 -2 -2 -6 -8 -1
-11

题目分析

首先设 d p [ i ] dp[i] dp[i]表示前 i i i场比赛,在参加比赛 i i i时能获得的最大快乐值。得递推方程:
d p [ i ] = m a x { d p [ j ] + d p [ i ] − ( i − j ) ∗ ( i − j − 1 ) 2 } ( a [ j ] ≤ a [ i ] ) dp[i] = max\{dp[j] + dp[i] - \frac{(i - j) * (i - j - 1)}{2}\} (a[j]\leq a[i]) dp[i]=max{dp[j]+dp[i]2(ij)(ij1)}(a[j]a[i])
欸,这玩意一看就很像斜率优化嘛,所以移项一下:
d p [ i ] + i ∗ ( i − 1 ) 2 = m a x { d p [ j ] − j ∗ ( j + 1 ) 2 + i ∗ 2 j } ( a [ j ] ≤ a [ i ] ) dp[i] + \frac{i*(i-1)}{2} = max\{dp[j]-\frac{j*(j+1)}{2}+i * 2j\}(a[j]\leq a[i]) dp[i]+2i(i1)=max{dp[j]2j(j+1)+i2j}(a[j]a[i])
那么,分析可得, ( 2 j , d p [ j ] − j ∗ ( j + 1 ) 2 ) (2j, dp[j] - \frac{j*(j+1)}{2}) (2j,dp[j]2j(j+1))为点,维护一个上凸包。
问题是这道题中,对于转移方程中的 j j j有限制。显然不能直接使用斜率优化。
处理方法如下:

  1. a a a数组的值从小到大离散化为 [ 1 , s i z e ] [1,size] [1,size],也可以说是求 a a a数组对应的rank数组
  2. 建立一颗覆盖范围为 [ 1 , s i z e ] [1,size] [1,size]的线段树,每个节点维护对应区间中点的上凸包
  3. 计算 d p [ i ] dp[i] dp[i]时,查找 a [ i ] a[i] a[i]对应的rank值,在线段树上查询 [ 1 , r a n k [ a [ i ] ] [1, rank[a[i]] [1,rank[a[i]]范围,在凸包上计算返回最大值,复杂度为 O ( l o g s i z e ) O(logsize) O(logsize)
  4. 插入 d p [ i ] dp[i] dp[i]对应的点时,从下层至上层依次更新凸包,复杂度为 O ( l o g s i z e ) O(logsize) O(logsize)
  5. 最后不要忘记不参加任何比赛的特殊情况

我自己的代码不够好看,特意贴上标程。

#include 
#if ( _WIN32 || __WIN32__ || _WIN64 || __WIN64__ )
#define INT64 "%I64d"
#else
#define INT64 "%lld"
#endif

#if ( _WIN32 || __WIN32__ || _WIN64 || __WIN64__ )
#define UNS64 "%I64u"
#else
#define UNS64 "%llu"
#endif
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;


#ifdef LOCAL
	#define eprintf(...) fprintf(stderr, __VA_ARGS__)
#else
	#define eprintf(...) 42
#endif

const int INT_INF = (int) 1e9 + 100;
const long long INF = (long long) 1e18 + 100;

const int LOG = 20;
const int LEVEL = (1 << LOG);
const int SIZE = (LEVEL << 1);



struct Func
{
	// y = -(x - st) * (x - (st - 1) ) / 2 + val
	// y = -x ^ 2 / 2 + x (st - 1 + st) / 2 - st * (st - 1) / 2 + val
	
	long long a_x2, b_x2, c_x2;
	
	Func() : a_x2(), b_x2(), c_x2() {}
	
	Func(long long st, long long val)
	{
		a_x2 = -1;
		b_x2 = 2 * st - 1;
		c_x2 = -st * (st - 1) + 2 * val;
	}

	long long getVal(long long x)
	{
		return (a_x2 * x * x + b_x2 * x + c_x2) / 2;
	}

	int getFirstPos(const Func &A) const
	{
		long long db_x2 = b_x2 - A.b_x2;
		long long dc_x2 = c_x2 - A.c_x2;

		// this - new, best on suffix
		// A - old, best on prefix
		// this - A   : increasing
		// x * b + c >= 0   <--->  x * 2b + 2c >= 0  <---> x * 2b >= -2c  <--->   x >= -2c / 2b
		
//		eprintf("2b = " INT64 ", 2c = " INT64 "\n", db_x2, dc_x2);
		if (db_x2 <= 0) throw; // incorrect order of adding Functions to Hull
		if (dc_x2 >= 0) throw; // we may return 0 for example, but still, it shouldn't happens

		return min( (-dc_x2 + db_x2 - 1) / db_x2, (long long) INT_INF );

	}
};


struct CHull
{
	vector <pair <Func, int> > hull;
	int ptr;

	CHull() : hull(), ptr() {}

	long long getVal(int i)
	{
		if (hull.empty() )
			return -INF;
		while (ptr + 1 < (int) hull.size() && hull[ptr + 1].second <= i)
			ptr++;
		return hull[ptr].first.getVal(i);
	}

	void addFunc(int i, long long val)
	{
		Func cur = Func(i, val);
		while (!hull.empty() )
		{
			int pos = hull.back().second;
			if (pos < i || cur.getVal(pos) < hull.back().first.getVal(pos) )
				break;
			hull.pop_back();
		}
	
		ptr = max(0, min(ptr, (int) hull.size() - 1) );

		if (hull.empty() )
			hull.emplace_back(cur, i);
		else if (hull.back().second < i && cur.getVal(i) > hull.back().first.getVal(i) )
		{
			hull.emplace_back(cur, i);
		}
		else
		{
			int st = cur.getFirstPos(hull.back().first);
			if (st < INT_INF)
			{
				hull.emplace_back(cur, st);
			}
		}

	}
};


struct Tree
{
	CHull hull[SIZE];
	
	Tree() : hull() {}

	long long getVal(int v, int tl, int tr, int i, int l, int r)
	{
		if (r < tl || tr < l) return -INF;
		if (l <= tl && tr <= r)
			return hull[v].getVal(i);

		int tm = (tl + tr) / 2;
		return max(
				getVal(2 * v, tl, tm, i, l, r),
				getVal(2 * v + 1, tm + 1, tr, i, l, r)
				);
	}

	long long getVal(int i, int x)
	{
		long long ans = getVal(1, 0, LEVEL - 1, i, 0, x);
//		eprintf("getVal(%d, %d) = " INT64 "\n", i, x, ans);
		return ans;
	}

	void addFunc(int i, int x, long long val)
	{
		int pos = x + LEVEL;
		while (pos >= 1)
		{
			hull[pos].addFunc(i, val);
			pos /= 2;
		}
	}
} tree;



const int N = (int) 1e6 + 100;
int a[N];
vector <int> vals;
int pos[N];

int main()
{
#ifdef LOCAL
//	freopen("input.txt", "r", stdin);
//	freopen("output.txt", "w", stdout);
#endif
	int n;
	scanf("%d", &n);
	for (int i = 0; i < n; i++)
	{
		scanf("%d", &a[i] );
		vals.push_back(a[i] );
	}

	sort(vals.begin(), vals.end() );
	vals.resize(unique(vals.begin(), vals.end() ) - vals.begin() );

	for (int i = 0; i < n; i++)
	{
		pos[i] = lower_bound(vals.begin(), vals.end(), a[i]) - vals.begin();
	}

	tree.addFunc(0, 0, 0);
	for (int i = 0; i < n; i++)
	{
//		eprintf("i = %d\n", i);
		int x = pos[i];
		long long cur = tree.getVal(i, x);
		tree.addFunc(i + 1, x, cur + a[i] );
	}
	long long ans = tree.getVal(n, LEVEL - 1);

	printf(INT64 "\n", ans);

	return 0;
}


你可能感兴趣的:(题解,动态规划,数据结构)