luogu 1484 种树 比较严格地证明贪心选择性
题目链接:https://www.luogu.org/problemnew/show/P1484
首先想到dp,但是复杂度是O(n*k),本题吃不消。
假设种k-1棵树时的最优解为Ak-1,种的树从小到大的下标是(x1,x2,x3,...,xi,...,xk-1),那么种k棵树的最优解一定可以通过从种k-1棵树的最优解中删除一棵树并增加两棵树在它两边或者只增加一棵树得到。证明如下:
首先证明如果删除了树,则插入的树一定要在删除的树的两边。因为假如某棵树a没有插入到被删除的树的两边,那么一定能够找到一棵被删除的树b,使得它旁边不是都有插入的树,否则,假设删除了n棵树,则至少n+1棵树在被删除的树旁边,又有一棵树不在被删除的树旁边,故总共插入至少n+2棵树,与插入n+1棵树矛盾。下面对b分情况讨论
- b两边没有被插入的树。
如果b所在点的值比a的小,那么可以在Ak-1中删掉b并且加上a,所得结果比Ak-1更优,与Ak-1是最优解矛盾。因此b所在点的值大于等于a的值。这时可以把删除b和插入a的操作删除,所得的结果不会比当前结果更差。对删除后所得的操作序列递归讨论。 - b有一边有插入的树。
该树记为c,那么与情况1类似,如果b所在点的值比的小,那么可以在Ak-1中删掉b并且加上c,所得结果比Ak-1更优,与Ak-1是最优解矛盾。因此b所在点的值大于等于c的值。这时可以把删除b和插入c的操作删除,所得的结果不会比当前结果更差。对删除后所得的操作序列递归讨论。
经过不断的递归讨论,每次能把一个删除操作和一个插入操作取消掉,最后得到的操作序列中要么没有删除操作(也就是只插入一棵树),要么有删除操作,并且插入的树都在删除的树的两边。
下面证明所有的删除和插入的树都是连在一起的。注意,只有一个插入时,也视作删除和插入的树是连在一起的。例如,用0表示没有树,x表示有树,那么多棵树删除一定是形似于0x0x0 -> x0x0x;用D表示删除树,A表示插入树,那么多棵树删除一定是形似于ADADA
证明:
用反证法证明。假设它们没有连在一起,那么显然能够找到一棵被删除的树,使得其两边不是都有被插入的树,将其记为a。下面对a分情况讨论。
- a两边没有被插入的树。此时显然一定能找到一棵被删除的树,使得它两边都有被插入的树。设这三棵树与a组成的集合为E。如果E的权值和大于0,那么可以通过在Ak-1中应用E中的树的相关操作,得到比Ak-1更优的解,与Ak-1是最优解矛盾。因此E的权值和小于等于0。这时可以将E中树的相关操作取消掉,对剩下的操作递归讨论。
- a一边有被插入的树。将a与旁边被插入的树作为E,同1中的证明步骤,可以证明可以将E中树的相关操作取消掉,对剩下的操作递归讨论。
经过不断的递归讨论,每次都能够减少删除操作和插入操作的个数,最后得到的操作序列中所有删除和插入的树都是连在一起的。
因此每次删除都是将一个形似于x0x0x的外围向外拓展,变成0x0x0。我们可以把形似x0x0x的节点组看作是一个未经过插入的节点,而每次对这样的节点插入,都将两边的点都吸收进来变成一个新的未经过插入的节点,对这个新的节点做“插入操作”的权值定义为吸收进来的两边的节点的权值和减去中间的节点的权值。根据上面已经证明出来的结果,种k棵树的最优解一定可以通过对种k-1棵树的最优解通过对未插入节点进行插入得到,显然对权值最大的未插入节点进行插入可以得到多种一棵树时的最优解。
具体解法:初始时所有节点都可以看作是未经过插入的节点,选择权值最大的进行插入,然后把两边的节点吸收进来形成一个新的未插入的节点,这样所有节点仍然都是未插入的节点,继续选择权值最大的进行插入即可。
代码:
#include <iostream>
#include <queue>
#include <set>
#include <cstring>
using namespace std;
#define MAXN 500010
#define MAXVAL 1000010
typedef long long LL;
int main() {
priority_queue<pair<LL, int> > pq;
set<pair<int, LL> > se;
int n, k;
static bool del[MAXN];
scanf("%d%d", &n, &k);
memset(del, 0, (n+1) * sizeof(del[0]));
LL ans = 0;
LL real = 0;
for (int i = 1; i <= n; ++i) {
LL v;
scanf("%lld", &v);
pq.push(make_pair(v, i));
se.insert(make_pair(i, v));
}
while (k) {
auto point = pq.top();
pq.pop();
LL v = point.first;
int i = point.second;
if (del[i]) continue;
ans += v;
real = max(real, ans);
auto it = se.find(make_pair(i, v));
auto tmp = it;
auto latter = it;
int new_v = -v;
if (it != se.begin()) {
--it;
new_v += it->second;
del[it->first] = true;
se.erase(it);
}
++latter;
if (latter != se.end()) {
new_v += latter->second;
del[latter->first] = true;
se.erase(latter);
}
se.erase(tmp);
se.insert(make_pair(i, new_v));
pq.push(make_pair(new_v, i));
--k;
}
cout << real;
return 0;
}