A – The Cow Gathering
首先,如果没有限制的话,其实从叶子结点来抽是一定有解的,可以考虑思考一颗以最后出口为根结点的外向树来理解这个逻辑关系。现在有 \(m\) 个有向边,初步思考可以把每个点都作为最后一个节点,然后来看新的图有没有环,这样是 \(\Theta(n^2)\)。
以 \(u\) 为根时,发现对于限制边 \(a \to b\) 而言,\(a\) 的子树肯定是非法的。仔细想想,如果我们有一个合法点,那么其实我们单用这个性质就可以求出所有非法的点。感性理解,假设全部覆盖完毕之后,发现我们的合法可行点肯定是可以构成一个唯一的子图、而不受根的影响。
// P5157.cpp #include <bits/stdc++.h> using namespace std; const int MAX_N = 1e5 + 200; int n, m, phead[MAX_N], current, mhead[MAX_N], deg[MAX_N]; bool vis[MAX_N], ans[MAX_N]; struct edge { int to, nxt; } edges[(MAX_N << 1) + MAX_N]; void addpath(int *head, int src, int dst) { edges[current].to = dst, edges[current].nxt = head[src]; head[src] = current++; } void clearExit() { for (int i = 1; i <= n; i++) puts("0"); exit(0); } void dfs(int u, int fa) { ans[u] = true; for (int i = phead[u]; i != -1; i = edges[i].nxt) if (!vis[edges[i].to] && edges[i].to != fa) dfs(edges[i].to, u); } int main() { memset(phead, -1, sizeof(phead)), memset(mhead, -1, sizeof(mhead)); scanf("%d%d", &n, &m); for (int i = 1, u, v; i <= n - 1; i++) scanf("%d%d", &u, &v), addpath(phead, u, v), addpath(phead, v, u), deg[u]++, deg[v]++; for (int i = 1, u, v; i <= m; i++) scanf("%d%d", &u, &v), addpath(mhead, u, v), deg[v]++, vis[u] = true; queue<int> q; for (int i = 1; i <= n; i++) if (deg[i] == 1) q.push(i); int pt = 0; while (!q.empty()) { int u = q.front(); q.pop(); if (deg[u] != 1) { if (deg[u] < 1) { pt = u; break; } clearExit(); } for (int i = phead[u]; i != -1; i = edges[i].nxt) { deg[edges[i].to]--; if (deg[edges[i].to] == 1) q.push(edges[i].to); } for (int i = mhead[u]; i != -1; i = edges[i].nxt) { deg[edges[i].to]--; if (deg[edges[i].to] == 1) q.push(edges[i].to); } } if (pt == 0) clearExit(); dfs(pt, 0); for (int i = 1; i <= n; i++) puts(ans[i] ? "1" : "0"); return 0; }
B – Sort It Out
这题就很仙了,我刚开始只发现一个没用的、图论的性质,后面证实确实没用。
其实是可以尝试想出来的(当然看了题解之后都会这么想)。发现取出一个相对有序的子序列,而让剩下的元素作为子集,确实是可以达到题目中所说的排序效果的。那么,求一个最小的子集,就需要一个最长的上升子序列。第一个问题解决。
那么,如何求第 \(k\) 小的子集呢?因为这个子集做字典序比较时会自动排序,那么我们可以贪心地想:如果我们剩下更多「小」的元素,那么子集的字典序就会变小、而子序列的字典序会变大。因为这是个排列,所以这玩意肯定会一一对应。我们只需要找出第 \(k\) 大的最长上升子序列即可。
找的方式有点像线段树上二分,我们可以算以第 \(i\) 个位置开头的最长子序列的数量,然后可以通过比大小、减去大小来决定要不要找更大的、长度相同的元素来进行字典需调整即可。
// P5156.cpp #include <bits/stdc++.h> using namespace std; const int MAX_N = 1e5 + 200; typedef long long ll; int n, ai[MAX_N], head[MAX_N], current; ll k; bool tag[MAX_N]; struct edge { int to, nxt; } edges[MAX_N]; struct node { int val; ll sum; } nodes[MAX_N], dp[MAX_N]; inline int lowbit(int x) { return x & (-x); } node merge(node a, node b) { if (a.val < b.val) swap(a, b); if (a.val == b.val) a.sum = min(a.sum + b.sum, ll(1e18)); return a; } void update(int x, node d) { for (; x; x -= lowbit(x)) nodes[x] = merge(nodes[x], d); } node query(int x) { node ret = node{0, 0}; for (; x <= n + 1; x += lowbit(x)) ret = merge(nodes[x], ret); return ret; } void addpath(int src, int dst) { edges[current].to = dst, edges[current].nxt = head[src]; head[src] = current++; } int main() { memset(head, -1, sizeof(head)); scanf("%d%lld", &n, &k); for (int i = 1; i <= n; i++) scanf("%d", &ai[i]); update(n + 1, node{0, 1}); for (int i = n; i >= 1; i--) { node tmp = query(ai[i] + 1); update(ai[i], dp[i] = node{tmp.val + 1, tmp.sum}); addpath(dp[i].val, i); } for (int len = query(1).val, ptr = 0; len; len--) for (int i = head[len]; i != -1; i = edges[i].nxt) { if (k > dp[edges[i].to].sum) k -= dp[edges[i].to].sum; else { tag[ai[edges[i].to]] = true; while (ptr < edges[i].to) dp[ptr++] = node{0, 0}; break; } } printf("%d\n", n - query(1).val); for (int i = 1; i <= n; i++) if (!tag[i]) printf("%d\n", i); return 0; }