[国家集训队]种树 贪心 堆

~~~题面~~~

题解:

  其实是一个神奇的贪心。如果m > n / 2,那么显然无解,否则肯定有解。如果没有相邻不能取的限制,那么直接贪心取前m大即可,但是有这个限制。所以考虑怎么维护这个性质。
  首先有一个结论,如果是贪心的取,对于一个点,要么取它旁边的两个,因为是贪心的取的,如果只取了旁边中的一个的话,那还不如就取它自己,而不取旁边的,因为只取旁边的一个肯定比取它小,还不会干扰别的树的取舍。

  那么基于这个结论,可以观察到,如果取x,那么被限制不能取的树是x - 1和x + 1,如果取它两边的数,那就么被限制的就是x, x + 2, x - 2,而x在取x的时候就已经被限制不能取第二次了,也就是从取x转变为取x + 1和x - 1时,多出来的限制只有x - 2, x + 2,所以如果取出x后,就把x - 1, x + 1合并成一个节点,那么x + 2, x - 2就相当于是这个新节点的相邻节点,于是就可以再像取x时一样做了。而因为取了这个新的被融合的节点,我们就需要舍弃x这个节点,因此代价应该是w[x  - 1] + w[x + 1] - w[x]。但被限制的标记应该要打在vis[x - 1]和vis[x + 1]上,因为被融合的新点是放在vis[x]的位置上的。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define R register int
 4 #define AC 201000
 5 #define LL long long
 6 int n, m, ans;
 7 int a[AC], last[AC], Next[AC];
 8 bool z[AC];
 9 
10 struct node{
11     int w, id;
12 };
13 
14 struct cmp{
15     bool operator () (node a, node b)
16     {
17         return a.w < b.w;
18     }
19 };
20 
21 priority_queue<node, vector<node>, cmp> q;
22 
23 inline int read()
24 {
25     int x = 0; char c = getchar(); bool z = false;
26     while(c > '9' || c < '0') 
27     {
28         if(c == '-') z = true;
29         c = getchar();
30     }
31     while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
32     if(!z) return x;
33     else return -x;
34 }
35 
36 void pre()
37 {
38     n = read(), m = read();
39     if(m > n / 2) 
40     {
41         printf("Error!\n");
42         exit(0);
43     }
44     for(R i = 1; i <= n; i ++) a[i] = read();
45 }
46 
47 void build()
48 {
49     for(R i = 1; i <= n; i ++) 
50     {
51         node x = (node){a[i], i};
52         last[i] = i - 1, Next[i] = i + 1;
53         if(!last[i]) last[i] = n;
54         if(Next[i] == n + 1) Next[i] = 1;
55         q.push(x);
56     }
57 }
58 
59 void change(int x)
60 {
61     z[x] = true;
62     Next[last[x]] = Next[x], last[Next[x]] = last[x];
63     Next[x] = last[x] = 0;//更新新坑周围的坑的前驱和后继
64 }
65 
66 void work()
67 {
68     node x;
69     for(R i = 1; i <= m; i ++)
70     {
71         while(z[q.top().id]) q.pop();//因为选了一个点之后,这个点旁边的两个点被标记,于是旁边的就不能选了,但是这个点可以再选一次
72         x = q.top();
73         q.pop();
74         ans += x.w;
75         int l = last[x.id], r = Next[x.id];//error!!!是x.id不是i啊。。。。
76         a[x.id] = a[l] + a[r] - a[x.id];//相当于合并了3个坑
77         node now = (node){a[x.id], x.id};
78         change(l), change(r);
79         q.push(now);
80     }
81     printf("%d\n", ans);
82 }
83 
84 int main()
85 {
86 //    freopen("in.in", "r", stdin);
87     pre();
88     build();
89     work();
90 //    fclose(stdin);
91     return 0;
92 }

 

posted @ 2018-09-06 16:32  ww3113306  阅读(217)  评论(0编辑  收藏  举报
知识共享许可协议
本作品采用知识共享署名-非商业性使用-禁止演绎 3.0 未本地化版本许可协议进行许可。