决策延后和随机映射

· · 算法·理论

写在前面

这个决策延后是个盗版。不是 DP 那个。

作为笔者,我也不知道这两个东西有什么关联。

但作为两个比较常见的 trick,就让我来整理一下吧。

合并延后

对于 n 个询问,如果可以按某种顺序排序后,对于每个点对 (i,j) 满足:

那么这些询问是可以进行延后处理的,其中的 f 为另一些限制,此时的延后处理是解决关于 f 的限制问题的。

延后处理的核心是两个数组 ansanss,分别记录了当前的实际答案与合并延后后的答案。

合并延后的目的是处理:现在无法贡献,但与以后的一起加可以贡献的情况。

### 例一、$^*$BagBag 本题为笔者的自创题。 有一个包容量为 $m$,$n$ 件物品,有重量 $w$ 和价值 $v$,以及一个多的魔法值 $k$,求最大的装货方式 $(a_1,a_2,a_3,\dots,a_p)$,使得 $m+\max\limits_{1\le i\le p}(k_{a_i})\ge \max\limits_{1\le i\le p}(w_{a_i})$。求 $\sum v_{a_i}$ 最大。 如果直接暴力的话肯定会出现现在加不了,后面加的了的问题。 考虑决策延后,记录两个变量 $ans,anss$ 分别表示当前的合法答案,合并延后的答案。 - 如果这个物品加不了,则 $anss$ 增加,$ans$ 不变。 - 如果这个物品可以加,则比这个物品重量小的都可以加,则 $ans\to anss$,并增加当前的答案。 这道题目是合并延后的入门题,是弱化版。 ```cpp using namespace std; const int N = 1e6 + 5; struct node { int v, w, k; } a[N]; bool cmp(node x, node y) { return x.k < y.k; } int main() { int n, m; cin >> n >> m; for(int i = 1; i <= n; i++) { cin >> a[i].w >> a[i].v >> a[i].k; } sort(a + 1, a + n + 1, cmp); int ans = 0, anss = 0; for(int i = 1; i <= n; i++) { if(m + a[i].k < a[i].w) { anss += a[i].v; } else { ans = anss = anss + a[i].v; } } cout << ans; } ``` ### 例二、AT_nikkei2019_qual_e Weights on Vertices and Edges 有一个图 $(V,E)$,有点权和边权,将这些边删除一些使得对于剩下的边,其所在的连通块的点权和大于等于这条边的边权,求最小删除的边数。 首先倒着来,改为最多能加的边,我们发现对于 $f$ 表示:点 $i$ 与点 $j$ 是否要合并,排序顺序为边权降序。 一个显然的错误是直接合并,如果符合就改。但可能单次合并不行,但合并成一个更大的连通块可行,所以这个贪心有问题。 这样的话对于点 $i$ 和 $j$ 要合并的情况下,如果 $i<j$,即 $w_i<w_j$,则 $w_i$ 的选择对 $w_j$ 是没有影响的,且如果 $j$ 可以,则 $i$ 也可以。 所以我们记录 $ans$ 表示合法的以 $i$ 为根的答案,$anss$ 表示以 $i$ 为根的所有答案。 为了方便,我们记 $sum_u$ 为 $u$ 所在的连通块的点权和,对于边 $(u,v,w)$: - 如果 $u,v$ 在一个连通块: - 如果 $sum_u\ge w$,则这条边加入没问题,记录 $ans_u = ans_u+1,anss_u=anss_u+1$。 - 否则加入有问题,则只记录 $anss_u = anss_u +1$。 - 如果 $u,v$ 不在同一个连通块: - 如果 $sum_u +sum_v < w$,则加入这条边有问题,但我们仍然将两个连通块合并,记录 $ans_u = ans_u+ans_v$,且 $anss_u = anss_u + anss_v+1$。 - 如果 $sum_u +sum_v \ge w$,则无问题,此时由于其满足上述性质,则 $u$ 和 $v$ 以前不行的边就可以了,所以记录 $ansu=anss_u = anss_u+anss_v+1$。 ```cpp #include <bits/stdc++.h> using namespace std; #define int long long const int N = 1e5 + 5; struct edge { int u, v, w; } a[N]; int fa[N], mx[N], sum[N], ans[N], ans1[N], n, m; bool cmp(edge x, edge y) { return x.w < y.w; } int Find(int u) { if(u == fa[u]) return u; return fa[u] = Find(fa[u]); } signed main() { cin >> n >> m; for(int i = 1; i <= n; i++) { cin >> sum[i]; fa[i] = i; } for(int i = 1; i <= m; i++) { cin >> a[i].u >> a[i].v >> a[i].w; } sort(a + 1, a + m + 1, cmp); int anss = 0; for(int i = 1; i <= m; i++) { int fu = Find(a[i].u), fv = Find(a[i].v); if(fu == fv) {//如果 w<sum(fu) 就加入答案,否则先等着 mx[fu] = max(mx[fu], a[i].w); if(mx[fu] <= sum[fu]) { ans[fu]++,ans1[fu]++; } else { ans1[fu]++; } } else { fa[fv] = fu; sum[fu] += sum[fv]; mx[fu] = max(max(mx[fu], mx[fv]), a[i].w); if(mx[fu] <= sum[fu]) { ans[fu] = ans1[fu] + ans1[fv] + 1; ans1[fu] = ans[fu]; } else { ans[fu] = ans[fu] + ans[fv]; ans1[fu] = ans1[fu] + ans1[fv] + 1; } } } for(int i = 1; i <= n; i++) { if(fa[i] == i) { anss += ans[i]; } } cout << m - anss; return 0; } ``` ## 随机映射 随机映射就是将元素映射为随机的值,然后用异或求和等方式将原来苛刻的条件映射成几个数值的比较。 ### 例一、[CSP-S2022] 星战 给你 $4$ 个操作,分别为删点、加点、删边、加边。问你每次加边后图是否是内向基环树。 内向基环树就是每一个点都有一个出边,我们可以利用这个性质。 我们给每一个点附上一个随机权值 $val_x$,如果一个点有出边就加上权值,在记录一下点作为终点的随机权值和 $sum_x$ 和满状态下的 $sum1_x$。 则对于 $4$ 个操作: - $sum_y\to sum_y-val_x,ans\to ans - val_x$,表明 $x\to y$ 的被删掉了。 - $ans\to ans-sum_x,sum_x = 0$,表明点 $x$ 死掉了。 - $sum_y \to sum_y + val_x,ans \to ans + val_x$,表明加入了边。 - $ans\to ans + sum1_x - sum_x$,表示加入剩下的边,然后 $sum_x \to sum1_x$。 如果 $ans$ 和 $\sum val_x$ 相等,则是 `Yes` 否则是 `No`。 习题:P2087 / P16726。