zoukankan      html  css  js  c++  java
  • [洛谷P1484] 种树

    题目类型:堆+贪心

    传送门:>Here<

    题意:有(N)个坑,每个坑可以种树,且获利(a[i])(可以为负)。任何相邻两个坑里不能都种树,问在最多种(K)棵树的前提下的最大获利

    解题思路

    第一眼觉得是(DP),但是数据太大(NK)显然不行……

    如果不约束相邻两个坑不能都种,那么显然是取最大的几个正数。只需排序求解即可

    回到问题,一步一步分析吧。

    假设(K=1),那么此时必然选最大的。

    假设(K=2),可以选择最大的(a[i]),并且对于所有(j eq i-1 且 j eq i+1),选择(a[j])。但是存在一个问题,加入存在这样的情况:(99 100 99)。因此还有可能的最优方案是不选择(a[i]),选取(a[i-1]和a[i+1])。并且我们会发现,出现这种情况时,必定同时选择(a[i-1]和a[i+1]),因为(a[i])才是最大的

    由于这种情况的存在,普通的做法似乎毫无头绪。联系网络流中我们引入了反向边的思想,意在反悔之前作出的决策。那么放在本题也一样。我们先去选择那个最大的,然后通过一种方式消除影响后选择两边的。于是,我们可以用一个大根堆维护所有的坑,选出最大的以后,删除最大值(a[i]),并推入(p=a[i-1]+a[i+1]-a[i])。如果(p>0),意味着(a[i-1]+a[i+1]>a[i])

    但是注意,并不是每次碰到这种情况我们都要去选,毕竟选择(a[i])只需要一颗树,而选择(a[i-1]+a[i+1])要两棵树。因此依然按照大根堆的规定来行使。容易发现,在堆里,我们不仅需要存值,还需要存位置

    下一步,如何实现?这是个比较困难的问题,在此之前还需要对问题进行深入分析

    前面我们得到结论,要么选择(a[i]),要么选择(a[i-1]+a[i+1])。这就好像(a[i-1] .. a[i+1])是一个节点一样,初始值是(a[i]),选过之后值成为了(a[i-1]+a[i+1]-a[i])。并且我们也会发现,选择两边的节点的话将会导致(a[i-2]和a[i+2])也不能选,正好像一个节点两侧的节点一样。

    联系缩点的思想,选择完一次以后,就可以将其左右缩点。此后这个点的权值就变了,但是依然是个正常的点。我们维护数组(L[i]和R[i])表示点(i)的左右侧(缩点之后)。初始时(L[i]=i-1, R[i]=i+1)。每一次缩点之后往左右两侧拓展即可。当然,切不可用(i-1)来代替左右侧,因为此时我们考虑的点全都得当成是缩完之后的点

    注意,缩点以后这个点内部的原先点的个数一定是奇数个。因此我们把信息全部存在中间那个点上,并且不允许访问那些其他被缩掉的点。用布尔数组记录,如果大根堆的堆顶访问到的是已经被标记过的则强行弹出。如果遇到负数的则意味着再选下去肯定不会优,直接结束。

    Code

    已压行

    /*By DennyQi 2018*/
    #include <cstdio>
    #include <queue>
    #include <cstring>
    #include <algorithm>
    #define  r  read()
    using namespace std;
    typedef long long ll;
    const int MAXN = 500010;
    const int INF = 1061109567;
    inline int Max(const int a, const int b){ return (a > b) ? a : b; }
    inline int Min(const int a, const int b){ return (a < b) ? a : b; }
    inline int read(){
        int x = 0; int w = 1; register char c = getchar();
        for(; c ^ '-' && (c < '0' || c > '9'); c = getchar());
        if(c == '-') w = -1, c = getchar();
        for(; c >= '0' && c <= '9'; c = getchar()) x = (x<<3) + (x<<1) + c - '0'; return x * w;
    }
    struct Tree{ ll val; int idx; };
    inline bool operator < (const Tree& a, const Tree& b){ return a.val < b.val; }
    int N,K,x,used[MAXN],L[MAXN],R[MAXN]; ll ans,a[MAXN];
    priority_queue <Tree> q;
    int main(){
    	N = r, K = r;
    	for(int i = 1; i <= N; ++i){ q.push((Tree){a[i]=r, i}); L[i] = i-1, R[i] = i+1; }
    	while(K--){
    		while(used[q.top().idx]) q.pop();
    		if(q.top().val <= 0) break;
    		ans += 1LL * q.top().val, x = q.top().idx; q.pop();
    		a[x] = a[L[x]] + a[R[x]] - a[x]; 
    		used[L[x]] = used[R[x]] = 1;
    		L[x] = L[L[x]], R[L[x]] = x; R[x] = R[R[x]], L[R[x]] = x;
    		q.push((Tree){1LL * a[x], x});
    	}
    	printf("%lld", ans);
    	return 0;
    }
    
  • 相关阅读:
    第七周作业
    第六周作业
    第五周作业
    第四周作业
    第三周作业
    第二周作业
    求最大值及下标
    查找整数
    抓老鼠
    第五周作业
  • 原文地址:https://www.cnblogs.com/qixingzhi/p/9537192.html
Copyright © 2011-2022 走看看