Codeforces 980E The Number Games - 贪心 - 树状数组

题目传送门

  传送点I

  传送点II

  传送点III

题目大意

   给定一颗有$n$个点的树,$i$号点的权值是$2^{i}$要求删去$k$个点,使得剩下的点仍然连通,并且总权值和最大,问删去的所有点的编号。

  其实这道题和虚树没有太大的关系,我只是提一下而已。

  因为点的权值很特殊,所以相当于要求剩下序列(从大到小)的字典序最大。

  然后过程就比较显然了。从大到小枚举点,判断能否保留它(计算加入它后,新的树的点数有没有超过限制)。保留它是指和已经被保留的点连通。

  这个相当于使得一些关键点连通,然后求出总点数。显然它可以用虚树来做。所以考虑虚树的构造算法,于是我们成功得到了一个时间复杂度为一个天文数字的算法。

  将一个关键点添加进已有的树(暂时把保留的点形成的树这么称呼吧)中,无非情况有两种:

  • 从这个关键点往上跳,直到跳到某个在已有的树中的点,经过点均被加入已有的树中。这种情况出现当且仅当关键点存在于已有的树的根(离原树钦定的根最近的一个点)在原树的子树中。
  • 这个关键点到已有的树的根的路径上所有点被加入已有的树。(其他情况)

  显然,这个过程不能纯暴力做。首先需要计算新添加的点的个数,如果能够保留它,那么暴力修改(因为修改的节点数等于$(n - k)$)。

  对于情况一,可以通过记录深度数组和倍增数组解决。

  对于情况二,求完LCA用树上距离公式。

  总时间复杂度$O(n\log n)$

Code

 1 /**
 2  * Codeforces
 3  * Problem#980E
 4  * Accepted
 5  * Time: 1918ms
 6  * Memory: 172700k
 7  */
 8 #include <bits/stdc++.h>
 9 using namespace std;
10 typedef bool boolean;
11 
12 const int N = 1e6 + 5, bzmax = 21;
13 
14 int n, m;
15 int cnt;
16 vector<int> *g;
17 int *in, *out;
18 int *dep;
19 boolean *vis;
20 int bz[N][bzmax];
21 
22 inline void init() {
23     scanf("%d%d", &n, &m);
24     m = n - m;
25     g = new vector<int>[(n + 1)];
26     in = new int[(n + 1)];
27     out = new int[(n + 1)];
28     dep = new int[(n + 1)];
29     vis = new boolean[(n + 1)];
30     memset(vis, false, sizeof(boolean) * (n + 1));
31     for (int i = 1, u, v; i < n; i++) {
32         scanf("%d%d", &u, &v);
33         g[u].push_back(v);
34         g[v].push_back(u);
35     }
36 }
37 
38 void dfs(int p, int fa) {
39     in[p] = ++cnt, dep[p] = dep[fa] + 1;
40     bz[p][0] = fa;
41     for (int i = 1; i < bzmax; i++)
42         bz[p][i] = bz[bz[p][i - 1]][i - 1];
43     for (int i = 0; i < (signed)g[p].size(); i++) {
44         int e = g[p][i];
45         if (e == fa)    continue;
46         dfs(e, p);
47     }
48     out[p] = cnt;
49 }
50 
51 int lca(int u, int v) {
52     if (dep[u] < dep[v])    swap(u, v);
53     if (in[u] <= in[v] && out[u] >= out[v])
54         return u;
55     for (int i = bzmax - 1, a; ~i; i--) {
56         a = bz[u][i];
57         if (!(in[a] <= in[v] && out[a] >= out[v]))
58             u = a;
59     }
60     return bz[u][0];
61 }
62 
63 inline void solve() {
64     dep[0] = 0, vis[n] = true, m -= 1, in[0] = 0, out[0] = 1428571;
65     dfs(1, 0);
66     int vr = n;
67     for (int i = n - 1; i; i--)    {
68         if (vis[i])    continue;
69         if (in[vr] <= in[i] && out[vr] >= out[i]) {
70             int u = i, v;
71             for (int j = bzmax - 1; ~j; j--) {
72                 v = bz[u][j];
73                 if (dep[v] >= dep[vr] && !vis[v])
74                     u = v;
75             }
76             if (dep[i] - dep[u] + 1 > m)    continue;
77             for (int j = i; j && !vis[j]; j = bz[j][0])
78                 vis[j] = true, m--;
79         } else {
80             int g = lca(vr, i);
81 //            cerr << dep[i] + dep[vr] - (dep[g] << 1) << endl;
82             if (dep[i] + dep[vr] - (dep[g] << 1) > m)    continue;
83             for (int j = i; j != bz[g][0] && !vis[j]; j = bz[j][0])
84                 vis[j] = true, m--;
85             for (int j = bz[vr][0]; !vis[j]; j = bz[j][0])
86                 vis[j] = true, m--;
87             vr = g;
88         }
89     }
90     for (int i = 1; i <= n; i++)
91         if (!vis[i])
92             printf("%d ", i);
93 }
94 
95 int main() {
96     init();
97     solve();
98     return 0;
99 }
Multiplication algorithm

  我们完美解决了这个问题?不。做人要有追求,1.9s真不爽。

  注意到$n$号点必定存在于已有的树中。那么直接钦定$n$号点作为原树的根。

  这样直接消灭掉了情况二。对于情况一,也可以稍微改一改。因为原树的根与已有的树的根重合,可以直接树差分加上树状数组计算出距离。

Code

 1 /**
 2  * Codeforces
 3  * Problem#980E
 4  * Accepted
 5  * Time: 888ms
 6  * Memory: 98300k
 7  */
 8 #include <bits/stdc++.h>
 9 using namespace std;
10 typedef bool boolean;
11 
12 typedef class IndexedTree {
13     public:
14         int *ar;
15         int s;
16 
17         IndexedTree():ar(NULL) {    }
18         IndexedTree(int s):s(s) {
19             ar = new int[(s + 1)];
20             memset(ar, 0, sizeof(int) * (s + 1));
21         }
22 
23         void add(int idx, int val) {
24             for ( ; idx <= s; idx += (idx & (-idx)))
25                 ar[idx] += val;
26         }
27 
28         int getSum(int idx) {
29             int rt = 0;
30             for ( ; idx; idx -= (idx & (-idx)))
31                 rt += ar[idx];
32             return rt;
33         }
34 
35         void add(int l, int r, int val) {
36             add(l, val), add(r + 1, -val);
37         }
38 }IndexedTree;
39 
40 int n, m;
41 int cnt;
42 vector<int> *g;
43 int *in, *out;
44 int *dep, *fas;
45 boolean *vis;
46 IndexedTree it;
47 
48 inline void init() {
49     scanf("%d%d", &n, &m);
50     m = n - m;
51     g = new vector<int>[(n + 1)];
52     in = new int[(n + 1)];
53     out = new int[(n + 1)];
54     dep = new int[(n + 1)];
55     fas = new int[(n + 1)];
56     vis = new boolean[(n + 1)];
57     it = IndexedTree(n);
58     memset(vis, false, sizeof(boolean) * (n + 1));
59     for (int i = 1, u, v; i < n; i++) {
60         scanf("%d%d", &u, &v);
61         g[u].push_back(v);
62         g[v].push_back(u);
63     }
64 }
65 
66 void dfs(int p, int fa) {
67     in[p] = ++cnt, dep[p] = dep[fa] + 1, fas[p] = fa;
68     for (int i = 0; i < (signed)g[p].size(); i++) {
69         int e = g[p][i];
70         if (e == fa)    continue;
71         dfs(e, p);
72     }
73     out[p] = cnt;
74 }
75 
76 inline void solve() {
77     dep[0] = 0;
78     dfs(n, 0);
79     vis[n] = true, it.add(in[n], out[n], 1), m--;
80     for (int i = n - 1; i; i--) {
81         if (vis[i])    continue;
82         int added = dep[i] - it.getSum(in[i]);
83         if (added > m)    continue;
84         for (int j = i; !vis[j]; j = fas[j])
85             it.add(in[j], out[j], 1), vis[j] = true, m--;
86     }
87     for (int i = 1; i <= n; i++)
88         if (!vis[i])
89             printf("%d ", i);
90 }
91 
92 int main() {
93     init();
94     solve();
95     return 0;
96 }

  

posted @ 2018-05-10 22:06  阿波罗2003  阅读(441)  评论(0编辑  收藏  举报