@bzoj - 2216@ [Poi2011]Lightning Conductor


@description@

已知一个长度为 n 的序列 a1, a2, ..., an。
对于每个1 <= i <= n,找到最小的非负整数 p 满足对于任意的 j,有:

\[a_j <= a_i + p - \sqrt{|i-j|} \]

input
第一行 n (1 <= n <= 500000)。
下面每行一个整数,其中第 i 行是 ai (0 <= ai <= 10^9)。

output
n行,第i行表示对于i,得到的p

sample input
6
5
3
2
4
2
4

sample output
2
3
5
3
5
4

@solution@

@part - 1@

我们先把式子变形:

\[a_j + \sqrt{|i-j|} - a_i <= p \]

求最小的 p 使得这个式子对于任何 j 都成立,相当于求 \(a_j + \sqrt{|i-j|}\) 的最大值。
我们可以分类处理 i < j 与 i > j 两种情况来去掉根号内的绝对值。现在仅讨论 i > j 的情况, i < j 的方法可以同理。

@part - 2@

那么怎么求这个东西的最大值呢?暴力做是 \(O(n^2)\) 的时间复杂度,好像也不能什么线段树斜率优化等诸如此类的优化来着。
这时候就要引入一个新方法:决策单调性。

决策单调性是这样定义的:对于 j < k,在点 i 处 k 比 j 优,则在 i 之后 k 会始终比 j 优。
或者等价的,令点 i 取到最优解的点(决策点)为 \(p_i\),则决策单调性为:\(p_i \le p_{i+1}\)
如果具有决策单调性,可以利用先前我们决策时的信息,排除掉一些不可能是最优解的点,以降低时间复杂度。

那么这道题具有决策单调性吗?观察 \(y=\sqrt x\) 这一个函数,直观上它的增长率越来越小(事实上从它的导数来看的确是这样的),所以越靠前的点对后面的点的贡献会越来越小。所以决策单调性好像是成立的。
证明……恕我太弱证不来 QAQ。
【反正我选择打表,打出来单调就假装它是有决策单调性】

@part - 3@

假如我们已知这道题具有决策单调性,那我们怎么才能利用这个性质呢?
我已知的有两种方法:

(1)分治。根据决策单调性 \(p_i \le p_{i+1}\),我们进行分治处理。
对于原数列上的某一个区间 \([l,r]\),假设它对应的决策点区间为 \([L, R]\),我们取 \(l, r\) 的终点 \(mid\),暴力扫一遍 \([L, R]\) 求出 \(mid\) 对应的决策点 \(Mid\)
则原问题分成两个规模更小的子问题:原区间 \([l, mid-1]\) 对应决策点区间 \([L, Mid-1]\),原区间 \([mid+1, r]\) 对应决策点区间 \([Mid+1, R]\)。分治即可。
时间复杂度 \(O(n\log n)\)
该方法具有一定的局限性:决策点的选取前后不能有依赖性。即我们如果要求 \(mid\) 所对的决策点,不能依赖于求解 \(1\dots mid-1\)的决策点。然而大部分的 dp 是满足不了这个性质的。

(2)单调队列 + 二分。还是根据决策单调性 \(p_i \le p_{i+1}\),所以我们每次加入新的决策点时,它所能影响的区间肯定是从末尾开始依次往前影响。
于是我们维护一个队列,每一次加入新的决策点时就开始弹出队尾,直到新的决策点影响到的点全部更新。
然而这样有一个问题:假如点 j 影响到的区间为 [l, r],现在加入 k,它所能影响的区间为 [p, r]。则我们必须更新点 j 影响到的区间 [l, p-1]。
更新还好办,关键是怎么找这个 p。这个时候我们就可以根据决策单调性进行二分。
可以发现每次加入一个决策点,只需要我们二分一次。故时间复杂度 \(O(n\log n)\)
该方法也具有一定的局限性:我们必须要支持快速求某一个决策点对另外一个点的贡献。否则这个方法的时间复杂度将快速退化。

@accepted code@

@version - 1@

这个是单调栈 + 二分过的。

#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 500000;
int a[MAXN + 5], pnt[MAXN + 5];
//pnt -> point -> 决策点 qwq 
void reverse(int A[], int n) {
	for(int i=1,j=n;i<j;i++,j--)
		swap(A[i], A[j]);
}
struct node{
	int le, ri;
	int num;
	node(int _l=0, int _r=0, int _n=0):le(_l), ri(_r), num(_n){}
}que[MAXN + 5];
int s, t;
bool better(int i, int j, int k) {
	return a[j] - a[i] + sqrt(i - j) >= a[k] - a[i] + sqrt(i - k);
}
int b_search(node p, int x) {
	int le = p.le, ri = p.ri;
	while( le < ri ) {
		int mid = (le + ri + 1) >> 1;
		if( better(mid, p.num, x) ) le = mid;
		else ri = mid - 1;
	}
	return le;
}
void get_point(int n) {
	s = 1, t = 0;
	for(int i=1;i<=n;i++) {
		while( s <= t && que[s].ri < i )
			s++;
		if( s <= t && que[s].le < i )
			que[s].le = i;
		while( s <= t ) {
			if( better(que[t].ri, i, que[t].num) ) {
				if( better(que[t].le, i, que[t].num) )
					t--;
				else {
					que[t].ri = b_search(que[t], i);
					break;
				}
			}
			else break;
		}
		if( s > t ) que[++t] = node(i, n, i);
		else if( que[t].ri != n )
			que[t+1] = node(que[t].ri+1, n, i), t++;
		if( s <= t ) pnt[i] = que[s].num;
	}
}
int ans[MAXN + 5];
int main() {
	int n; scanf("%d", &n);
	for(int i=1;i<=n;i++)
		scanf("%d", &a[i]);
	get_point(n);
	for(int i=1;i<=n;i++)
		ans[i] = max(ans[i], int(ceil(a[pnt[i]] - a[i] + sqrt(i - pnt[i]))));
	reverse(a, n); get_point(n); reverse(pnt, n); reverse(a, n);
	for(int i=1;i<=n;i++)
		ans[i] = max(ans[i], int(ceil(a[n-pnt[i]+1] - a[i] + sqrt(n-pnt[i]+1 - i))));
	for(int i=1;i<=n;i++)
		printf("%d\n", ans[i]);
}

@version - 2@

这个是用分治过的。

#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 500000;
int a[MAXN + 5], pnt[MAXN + 5];
//pnt -> point -> 决策点 qwq 
void reverse(int A[], int n) {
	for(int i=1,j=n;i<j;i++,j--)
		swap(A[i], A[j]);
}
bool better(int i, int j, int k) {
	return a[j] - a[i] + sqrt(i - j) >= a[k] - a[i] + sqrt(i - k);
}
void get_point(int l, int r, int L, int R) {
	if( l > r ) return ;
	int mid = (l + r) >> 1, nw = L;
	for(int i=L;i<=R&&i<=mid;i++)
		if( better(mid, i, nw) ) nw = i;
	pnt[mid] = nw;
	get_point(l, mid-1, L, nw);
	get_point(mid+1, r, nw, R);
}
int ans[MAXN + 5];
int main() {
	int n; scanf("%d", &n);
	for(int i=1;i<=n;i++)
		scanf("%d", &a[i]);
	get_point(1, n, 1, n);
	for(int i=1;i<=n;i++)
		ans[i] = max(ans[i], int(ceil(a[pnt[i]] - a[i] + sqrt(i - pnt[i]))));
	reverse(a, n); get_point(1, n, 1, n); reverse(pnt, n); reverse(a, n);
	for(int i=1;i<=n;i++)
		ans[i] = max(ans[i], int(ceil(a[n-pnt[i]+1] - a[i] + sqrt(n-pnt[i]+1 - i))));
	for(int i=1;i<=n;i++)
		printf("%d\n", ans[i]);
}

@details@

平方根不能取整,不然会翻车(决策单调性不能成立)

当初做的时候,硬是要简化程序,把整个数组翻转过来再做。结果好多细节,WA 了几遍才过。
早知道我就直接复制粘贴了 TWT。

posted @ 2019-01-03 14:05  Tiw_Air_OAO  阅读(185)  评论(0编辑  收藏  举报