1. 程式人生 > 實用技巧 >[Luogu] CF515E Drazil and Park

[Luogu] CF515E Drazil and Park

\(Link\)

Description

有一隻猴子,他生活在一個環形的公園裡。有\(n\)棵樹圍繞著公園。第\(i\)棵樹和第\(i+1\)棵樹之間的距離是\(d_i\),而第\(n\)棵樹和第一棵樹之間的距離是\(d_n\)。第\(i\)棵樹的高度是\(h_i\)​ 。

這隻猴子每天要進行晨跑。晨跑的步驟如下:

  • 他先選擇兩棵樹;
  • 然後爬上第一棵樹;
  • 再從第一棵樹上下來,接著圍繞著公園跑(有兩個可能的方向)到第二棵樹,然後爬上第二棵樹;
  • 最後從第二棵樹上下來。

但是有一些小孩會在連續的一些樹上玩耍。所以猴子不能經過這些樹。

比如現在猴子選擇的第\(x\)棵和第\(y\)棵樹,那麼該早晨他消耗的能量是\(2(h_x+h_y)+dist(x,y)\)

。由於某一條路徑是被小孩子佔據的,所以他只能跑另外一條,因此\(dist(x,y)\)是確定的。

現在給出第\(i\)天,孩子們會在第\(a_i\)棵樹和\(b_i\)棵樹之間玩耍。具體的,如果\(a_i≤b_i\) ,那麼孩子玩耍的區間就是 \([a_i,b_i]\),否則孩子玩耍的區間就是\([ai,n]⋃[1,bi]\)

請幫助這隻猴子找出兩棵樹,讓他晨跑的時候他能夠消耗最大的能量。

Solution

可能會想到分別最大化,但這時求出來的\((x,y)\)很有可能不是同一對。

所以先斷環成鏈,再對\(d_i\)做一遍字首和。這時題目就轉化為最大化\(2h_x+2h_y+sum_y-sum_x\)

。套路:把\(x\)\(y\)的項分別處理,這時要使\(2h_y+sum_y\)最大,\(sum_x-2h_x\)最小。預處理\(ST\)表即可。

要注意首先要依據題意,對詢問區間求補集,然後算出來的\(x\)\(y\)可能是一樣的,不符合題意,這時只需在\([l,pos-1]\)\([pos+1,r]\)中分別查詢最值,在計算比較即可。所以\(ST\)表維護的是最值的下標。

Code

#include <bits/stdc++.h>

using namespace std;

#define ll long long

int n, m, lg[200005], d[200005], h[200005];

ll s1[200005], s2[200005], sum[200005], mn[200005][20], mx[200005][20];

int read()
{
	int x = 0, fl = 1; char ch = getchar();
	while (ch < '0' || ch > '9') { if (ch == '-') fl = -1; ch = getchar();}
	while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + ch - '0'; ch = getchar();}
	return x * fl;
}

int query1(int x, int y)
{
	if (x > y) return 0;
	int k = lg[y - x + 1];
	ll p = s1[mx[x][k]], q = s1[mx[y - (1 << k) + 1][k]];
	if (p > q) return mx[x][k];
	else return mx[y - (1 << k) + 1][k];
}

int query2(int x, int y)
{
	if (x > y) return 0;
	int k = lg[y - x + 1];
	ll p = s2[mn[x][k]], q = s2[mn[y - (1 << k) + 1][k]];
	if (p < q) return mn[x][k];
	else return mn[y - (1 << k) + 1][k];
}

int main()
{
	n = read(); m = read();
	for (int i = 1; i <= n; i ++ )
	{
		d[i % n + 1] = read();
		d[i % n + n + 1] = d[i % n + 1];
	}
	for (int i = 1; i <= n; i ++ )
	{
		h[i] = read();
		h[i] <<= 1;
		h[i + n] = h[i];
	}
	for (int i = 1; i <= (n << 1); i ++ )
		sum[i] = sum[i - 1] + (ll)(d[i]), lg[i] = (int)(log2((double)(i)));
	s1[0] = -2e15; s2[0] = 2e15;  
	for (int i = 1; i <= (n << 1); i ++ )
	{
		s1[i] = sum[i] + h[i];
		s2[i] = sum[i] - h[i];
		mn[i][0] = mx[i][0] = i; 
	}
	for (int j = 1; j <= 19; j ++ )
	{
		for (int i = 1; i + (1 << j) <= (n << 1); i ++ )
		{
			ll x, y;
			x = s1[mx[i][j - 1]], y = s1[mx[i + (1 << (j - 1))][j - 1]];
			if (x > y) mx[i][j] = mx[i][j - 1]; else mx[i][j] = mx[i + (1 << (j - 1))][j - 1];
			x = s2[mn[i][j - 1]], y = s2[mn[i + (1 << (j - 1))][j - 1]];
			if (x < y) mn[i][j] = mn[i][j - 1]; else mn[i][j] = mn[i + (1 << (j - 1))][j - 1];
		}
	}
	while (m -- )
	{
		int l = read(), r = read(), pos1, pos2;
		if (l <= r) pos1 = query1(r + 1, l + n - 1), pos2 = query2(r + 1, l + n - 1);
		else pos1 = query1(r + 1, l - 1), pos2 = query2(r + 1, l - 1);
		if (pos1 != pos2) printf("%lld\n", s1[pos1] - s2[pos2]);
		else
		{
			int pos3, pos4; ll res1, res2;
			if (l <= r)
			{
				pos3 = query1(r + 1, pos1 - 1), pos4 = query1(pos1 + 1, l + n - 1), res1 = max(s1[pos3], s1[pos4]) - s2[pos2];
				pos3 = query2(r + 1, pos2 - 1), pos4 = query2(pos2 + 1, l + n - 1), res2 = s1[pos1] - min(s2[pos3], s2[pos4]);
			}
			else
			{
				pos3 = query1(r + 1, pos1 - 1), pos4 = query1(pos1 + 1, l - 1), res1 = max(s1[pos3], s1[pos4]) - s2[pos2];
				pos3 = query2(r + 1, pos2 - 1), pos4 = query2(pos2 + 1, l - 1), res2 = s1[pos1] - min(s2[pos3], s2[pos4]);
			}
			printf("%lld\n", max(res1, res2));
		}
	}
	return 0;
}