zoukankan      html  css  js  c++  java
  • Evanyou Blog 彩带

    2019/11/14 更新日志:

    近期发现这篇题解有点烂,更新一下,删繁就简,详细重点。代码多加了注释。就酱紫啦!


    正解步骤

    1. 我们需要先算美妙度的前缀和,并初始化RMQ。

    2. 循环 (i)(1)(n) ,因为以i为起点的 和弦 终点必定是 (i + L - 1)(i + R - 1) 之间,所以只要在区间内用RMQ取 超级和弦 ,并加入以美妙度从小排到大的优先队列中。

    3. 取出堆顶元素,将美妙度加入 (ans) ,并将元素切为从 (当前元素的左边界 到 当前元素终点 - 1) 和 ((当前元素终点 + 1 到 当前元素右边界)) 两个部分,并再次加入优先队列,依次进行 (k) 次。

    4. 输出答案即可


    为什么要使用前缀和 and RMQ?

    数据范围是500000,很明显,为了优化需要前缀和 and RMQ。

    前缀和最明显的用处,是可以优化一个用来循环累加和的 (n) 。而 (RMQ) ,显然区间最值符合题目要求。

    这两个算法询问答案都是O(1)。前缀和后面减去前面,RMQ只需要初始化一下,然后O(1)询问即可。


    为什么第三步要切开元素并放入优先队列? 直接累加前 (k) 个元素不行么?

    首先,我们肯定可以确定:优先队列中第一大的和弦一定是 (全局) 最大的和弦。 不要问我怎么证明

    那么优先队列中第二大的和弦一定是 (全局) 次大的和弦么?这就不一定了。

    所以我们需要切开元素并放入优先队列,保证每次取出来的元素一定是全局大小排名的元素

    自己拿出纸和笔,结合题解自己思考,在草稿纸上演算一下,就懂了


    那我就把解题思路放上吧233

    实在还有问题,私信本人233

    感谢老师 @apple365 的思路指引。

    AC代码

    #include<bits/stdc++.h>
    #include<cctype>
    #pragma GCC optimize(2)
    
    #define in(a) a = read()
    #define out(a) write(a)
    #define outn(a) out(a),putchar('
    ')
    
    #define ll long long
    #define Min(a,b) a < b ? a : b
    #define Max(a,b) a > b ? a : b
    #define rg register
    #define New ll
    
    using namespace std;
    
    namespace IO_Optimization{
    
    	inline New read()
    	{
    	    New X = 0,w = 0;
    		char ch = 0;
    
    		while(!isdigit(ch))
    		{
    			w |= ch == '-';
    			ch=getchar();
    		}
    	    while(isdigit(ch))
    		{
    			X = (X << 3) + (X << 1) + (ch ^ 48);
    			ch = getchar();
    		}
    	    return w ? -X : X;
    	}
    
    	inline void write(New x)
    	{
    	     if(x < 0) putchar('-'),x = -x;
    	     if(x > 9) write(x/10);
    	     putchar(x % 10 + '0');
    	}
    
    	#undef New
    }
    using namespace IO_Optimization;//上面一坨优化的东西不用在意 
    
    const int MAXN = 500000 + 2;//定义常亮 
    
    int n,k,L,R;
    int sum[MAXN],lg[MAXN],dp[MAXN][20],pos[MAXN][20];
    //  前缀和    lg2值
    //dp[i][j]表示i的2^j次方祖先  pos数组来记录最佳位置 
    ll ans;
    struct Node
    {
    	int start, left, right, t, val; 
    	//超级和弦的起点 左、右边界 最值位置 最值 
    	bool operator < (const Node &next) const
    	{
    		return val < next.val; //最值从大到小排序 
    	} 
    };
    
    inline void RMQ_init() //预处理 
    { 
    	for(rg int j = 1;j <= 20; ++j)
    		for(rg int i = 1;i + (1 << j) - 1 <= n; ++i)
    		{
    			if(dp[i][j - 1] > dp[i + (1 << (j - 1))][j - 1])
    			{
    				dp[i][j] = dp[i][j - 1];
    				pos[i][j] = pos[i][j - 1];//更新最值位置 
    			}
    			else
    			{
    				dp[i][j] = dp[i + (1 << (j - 1))][j - 1];
    				pos[i][j] = pos[i + (1 << (j - 1))][j - 1];
    			}
    		}
    	return;
    } 
    
    inline int RMQ_query(int l, int r) //返回最值的位置 
    {
    	int t, tmp = lg[r - l + 1];
    	if(dp[l][tmp] > dp[r - (1 << tmp) + 1][tmp])
    		t = pos[l][tmp];
    	else t = pos[r - (1 << tmp) + 1][tmp];
    	return t;
    }
    
    int main()
    {
    	in(n),in(k),in(L),in(R);
    	lg[0] = -1;//lg2(0) = -1,方便后面预处理lg2值 
    	for(rg int i = 1;i <= n; ++i)
    	{
    		int a = read();//读入音符 
    		sum[i] = sum[i - 1] + a; //前缀和 
    		lg[i] = lg[i >> 1] + 1; //预处理lg2值 
    		dp[i][0] = sum[i];
    		pos[i][0] = i; //初始化最大值的位置 
    	}
    	RMQ_init();//初始化 
    	priority_queue<Node> pq; //定义优先队列 
    	for(rg int i = 1;i + L - 1 <= n; ++i) //计算每个位置最大的超级和弦 
    	{
    		int t = RMQ_query(i + L - 1, Min(n, i + R - 1)); 
    		Node cur;
    		cur.val = sum[t] - sum[i - 1]; //由前缀和取最大值 
    		cur.t = t;
    		cur.start = i; //当前超级和弦的起始位置 
    		cur.left = i + L - 1; //当前的左边界 
    		cur.right = Min(n,i + R - 1); //当前的右边界 
    		pq.push(cur); //入堆 
    	}
    
    	for(rg int i = 1;i <= k; ++i) //取k次堆顶的值 
    	{
    		Node cur = pq.top();
    		pq.pop();
    		ans = ans + cur.val; //累加结果 
    		Node next;
    
    		if(cur.t > cur.left) //当前取最值的位置 大于 当前和弦的 左边界 
    		{
    			next.start = cur.start;
    			next.left = cur.left;
    			next.right = cur.t - 1; //新的右边界 
    			next.t = RMQ_query(next.left, next.right);
    			next.val = sum[next.t] - sum[next.start - 1];
    			pq.push(next);
    		}
    
    		if(cur.t < cur.right) //当前取最值的位置 小于 当前和弦的 右边界 
    		{
    			next.start = cur.start;
    			next.left = cur.t + 1; //新的左边界 
    			next.right = cur.right;
    			next.t = RMQ_query(next.left, next.right);
    			next.val = sum[next.t] - sum[next.start - 1];
    			pq.push(next);
    		}
    	}
    	outn(ans);
    	return 0;
    }
    

    END.

  • 相关阅读:
    Js常用的函数
    数组和集合的相互转换
    Java中的volatile的作用和synchronized作用
    mac下使用mysql控制台命令行
    几种优化ajax的执行速度的方法
    解决Macbook网络连接成功但是图标一直显示正在查找网络问题
    Android Stdio 中的Rendering Problems Android N requires the IDE to be running with Java 1.8 or later Install a supported JDK解决办法
    Activiti 中的ACT_RU_TASK表中的EXECUTION_ID和PROC_INST_ID区别
    Ibatis学习总结2--SQL Map XML 配置文件
    Ibatis学习总结1--ibatis简介和SQL Maps
  • 原文地址:https://www.cnblogs.com/CJYBlog/p/LG2048.html
Copyright © 2011-2022 走看看