• @bzoj



    @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(nlog n))
    该方法具有一定的局限性:决策点的选取前后不能有依赖性。即我们如果要求 (mid) 所对的决策点,不能依赖于求解 (1dots mid-1)的决策点。然而大部分的 dp 是满足不了这个性质的。

    (2)单调队列 + 二分。还是根据决策单调性 (p_i le p_{i+1}),所以我们每次加入新的决策点时,它所能影响的区间肯定是从末尾开始依次往前影响。
    于是我们维护一个队列,每一次加入新的决策点时就开始弹出队尾,直到新的决策点影响到的点全部更新。
    然而这样有一个问题:假如点 j 影响到的区间为 [l, r],现在加入 k,它所能影响的区间为 [p, r]。则我们必须更新点 j 影响到的区间 [l, p-1]。
    更新还好办,关键是怎么找这个 p。这个时候我们就可以根据决策单调性进行二分。
    可以发现每次加入一个决策点,只需要我们二分一次。故时间复杂度 (O(nlog 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
    ", 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
    ", ans[i]);
    }
    

    @details@

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

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

  • 相关阅读:
    Flutter Icons 内置图标库,全套Material图标
    解决cannot connect to daemon at tcp:5037: cannot connect to 127.0.0.1:5037: 由于目标计算机积极拒绝,无法连接。 (10061).
    mavenCentral()、jcenter()、google()仓库
    flutter doctor检查出现多个Android Studio解决办法
    Oracle trunc 函数用法详解
    将博客搬至CSDN
    Yii2 高级模板不使用Apache配置目录,将前后台入口移到根目录
    报警告session_regenerate_id(): Failed to create(read) session ID: files (path: N;/path)
    yii2GridView的简单使用
    yii 表单如何写,action指向哪里?
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/10213838.html
Copyright © 2020-2023  润新知