题目传送门

  传送点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

相关文章: