• 「NOI2010」 超级钢琴(K大)


    opening

    题目链接

    经典题了,套路题,没做过就挺吃亏的,今天做了一下,确实跟今天 T1 的其中一种方法完全一样啊。

    ouline

    给定一个序列 (a(-1000 <= a_i <= 1000)) ,定义一个区间 ((l, r)) 的价值为区间和,求最大的 K 个区间的 sum。

    说白了就是求区间 K 大。

    solution

    第 K 大问题挺经典了吧,方法也不少,这里先讲关于这道题的吧。

    首先区间最大和一定是以 (i) 为左端点的 最大值 中 最大的那一个。

    如果我们能求出以 (i) 为左端点的 第 (j) 大值,那么这道题就可以做了。

    我们定义以 (i) 为左端点的合法右端点区间为 (L, R),那么我们可以用 RMQ 求出最大值的位置 (d),然后我们把区间分成 (L, d-1)(d+1, R) 两部分,易知次大值u一定是这两个区间的最大值之一,所以我们把这两个区间的次大值扔进去,后面反复重复该过程,知道求够 K 个为止。

    显然这样做是对的吧,而且复杂度也是非常对劲的 (O(nlogn)),所以这道题就解决了。

    std

    // by longdie 
    #include <bits/stdc++.h> 
    #define ll long long 
    using namespace std; 
    const int N = 5e5 + 5;
    ll ans;  
    int n, K, L, R, a[N], sum[N], f[N][20], id[N][20], lg[N]; 
    struct node {
    	int val, x, l, r, p; 
    	node() {}
    	node(int a, int b, int c, int d, int e) { val = a, x = b, l = c, r = d, p = e; }
    	friend bool operator < (const node &a, const node &b) { return a.val < b.val; } 
    } ; priority_queue<node> q; 
    inline int get(int l, int r, int &x) {
    	int k = lg[r - l + 1]; 
    	if(f[l][k] >= f[r-(1<<k)+1][k]) { x = id[l][k]; return f[l][k]; }
    	else { x = id[r-(1<<k)+1][k]; return f[r-(1<<k)+1][k]; }
    }
    signed main() {
    	scanf("%d%d%d%d", &n, &K, &L, &R); 
    	lg[1] = 0, lg[2] = 1; 
    	for(register int i = 3; i <= n; ++i) lg[i] = lg[i/2] + 1; 
    	for(register int i = 1; i <= n; ++i) scanf("%d", &a[i]), sum[i] = sum[i-1] + a[i]; 
    	for(register int i = 1; i <= n; ++i) f[i][0] = sum[i], id[i][0] = i; 
    	for(register int j = 1; j <= 18; ++j) 
    		for(register int i = 1; (i+(1<<j)-1) <= n; ++i) {
    			if(f[i][j-1] >= f[i+(1<<j-1)][j-1]) id[i][j] = id[i][j-1], f[i][j] = f[i][j-1]; 
    			else id[i][j] = id[i+(1<<j-1)][j-1], f[i][j] = f[i+(1<<j-1)][j-1]; 
    		}
    	for(register int i = 1, d; i <= n; ++i) {
    		if(i + L - 1 > n) break; 
    		int tmp = 0; 
    		tmp = get(i + L - 1, min(n, i + R - 1), d) - sum[i-1]; 
    		q.push(node(tmp, i, i+L-1, min(n, i+R-1), d));
    		//cout << tmp << '
    '; 
    	}
    	for(register int i = 1, d; i <= K; ++i) {
    		node t = q.top(); q.pop(); 
    		ans += t.val; 
    		int s = t.x, l = t.l, r = t.r, p = t.p; 
    		if(p > l) {
    			int tmp = get(l, p-1, d) - sum[s-1];
    			q.push(node(tmp, s, l, p-1, d)); 
    		}
    		if(p < r) {
    			int tmp = get(p+1, r, d) - sum[s-1];
    			q.push(node(tmp, s, p+1, r, d)); 
    		}
    	}
    	cout << ans << '
    '; 
    	return 0; 
    }
    

    extend

    小编大概总结了一下 K 大的经典套路吧:

    1. 就是上面说的那种方法,每次确定第 (j) 大的区间范围,并且能用 RMQ 来维护。
    2. 第二种可以二分答案,这样显然可以降低难度对吧,然后问题就变化为了求个数问题。
    3. 就是比较常规方法了,用主席树,平衡树,KD-Tree等数据结构维护,这个似乎需要具体问题具体分析。

    基本上这种问题都是用堆实现的。

  • 相关阅读:
    。【自学总结 2】------3ds Max 菜单
    。【自学总结 1】------3ds Max 界面
    。求推荐一个usb集线器的购买网址
    (翻译)Importing models-Models
    (翻译)Importing models-FBX Importer, Rig options
    [游戏开发-学习笔记]菜鸟慢慢飞(18)-迷宫
    [游戏开发-学习笔记]菜鸟慢慢飞(17)- c#-委托简记(匿名方法,Lambda表达式)
    [游戏开发-学习笔记]菜鸟慢慢飞(16)- Unity3D-Android插件问题集锦
    [游戏开发-学习笔记]菜鸟慢慢飞(15)- Unity3D-图片压缩
    [游戏开发-学习笔记]菜鸟慢慢飞(14)- ScrollView刷新
  • 原文地址:https://www.cnblogs.com/longdie/p/14530133.html
Copyright © 2020-2023  润新知