【NOTE】Part 3
枚举子集的\(\text{trick}\):
for (int S=1;S<(1<<n);S++)
for (int j=(S-1)&S;j;j=(j-1)&S)
f[S]=max(f[S],f[j]+f[S^j]);
\(bitset\)用法
//(1) 默认赋值全0
bitset<7> a; //a:0000000
//(2) 赋数的时候赋补码
bitset<7> a(17); //a:0010001
bitset<7> a(-17); //a:1101111
--------------
//(3) 支持下标从0开始访问。小下标对应小端(右)
bitset<7> a("1000101");
for (int i=0;i<7;i++)
printf("%d ",a[i]);
/*output:1010001*/
--------------
//(4) a.count() 返回1的个数
//(5) a.size() 返回长度
//(6) a.test(x) 返回第x位是否为1
//(7) a.any() 返回是否含1
//(8) a.none() 返回是否全0
//(9) a.all() 返回是否全1
//(10) a.set() 全赋为1
//(11) a.set(x,k) 将第x位赋为k
//(12) a.reset() 全赋为0
//(13) a.reset(x) 将第x位赋为0
//(14) a.flip() 全部取反
//(15) a.flip(x) 将第x位取反
//(16) a.to_string() 转换成string
//(17) a.to_ullong() 转换成unsigned long long
背包写法杂堆
泛化物品
对于某一个泛化物品,在给其 \(w\) 的容量/费用时,其价值为 \(h(w)\) 。
背包就可以看作一个泛化物品。背包的求和也可以算是泛化物品的求和。如背包\(f_i=f_j+f_k\):
合并复杂度\(O(V^2)\)
树形背包的复杂度
两种实现方法。
第一种实现方法是,把背包的容量先扩充该子树大小,随后合并背包。复杂度是 \(O(nm^2)\)
第二种实现方法是枚举两个背包大小,直接合并。复杂度是 \(O(nm)\) ,怎么来的我不会证。
siz[x]=1;
for (int i=h[x],y;i;i=e[i].nex)
{
y=e[i].to;
if (y==fa[x]) continue;
fa[y]=x;
dfs(y);
for (int j=siz[x];j>=0;j--)
for (int k=1;k<=siz[y];k++)
f[x][j+k]=max(f[x][j+k],f[x][j]+f[y][k]);
siz[x]+=siz[y];
}
按秩合并的可撤销并查集
按秩合并就是高度比较小的接到高度比较大的上面去。
可以支持倒序撤销(加了啥撤啥)。具体实现是用栈存储旧状态的根父亲与树高。
一系列求区间 \(mex\) 的方式
我之前对求 \(mex\) 的复杂度一直有什么误解。草。
集合mex(静态):求一个集合的 \(mex\),操作只有添加元素,只需要用个数组标记,弄一个变量指向当前的 \(mex\),一旦该位置被填上就一直让这个变量++直到一个没有标记的位置。如果值域很大,直接让 \(>n\) 的部分都记作 \(n+1\) 即可。
集合mex(带修):求一个集合的 \(mex\),操作为添加一个数或删除一个数。使用一个 \(set\) 维护没有出现过的数,如果添加该数就从 \(set\) 中将其删除,如果删除了该数就将其添加进 \(set\) 。查询即查询 \(set\) 最小值。
区间mex(静态离线)
这仨做法都要离线。
-
(莫队+ \(set\))因为 \(set\) 支持增加/减少一个元素后求解答案,很好搞,根号带 \(log\) 有点吃。
-
(线段树)先处理出所有前缀的答案,即 \(mex[1\dots i]\) 。暂且记作在区间左端点为 \(l\) 下的 \(mex[i]\) 。每次删去最前面的一个元素 \(a_l\),考虑其对答案的影响。\(mex[nex_l]\text{~}mex[n]\) 是不会受到影响的,而 \(mex[l+1]\text{~}mex[nex_l-1]\) 变成原值和 \(a_l\) 取 \(min\) 。我忘了这是啥神奇做法啊,在李超树的时候见过,我覆盖一系列区间,查询单点的时候一直查到底,路径上所有区间值的 \(min\) 就是我要的 \(min\) 。
-
(权值线段树)存每个数最晚出现的时间戳。找最小的最晚出现时间戳 \(\leq l\) 的值。在权值线段树上二分即可。
区间mex(静态在线)
在权值线段树那个做法的基础上可持久化即可。
变式
对于每个 \(i\) 求有多少个区间的区间 \(mex\) 为 \(i\) 。
先考虑单独的一个 \(k\) 。
我们使用权值线段树记录每个值最晚出现时间戳 \(t_w\)。考虑到处理恰好 \(i\) 并不好做,我们处理 \(mex \geq i\) 的区间数,然后单容斥一下。
枚举区间右端点,每次添加进该点 \(i\) 。左端点即 \(min\{t_w\}, w\leq a_i\)
再看多询问的,不大好做的。上面那玩意要是用线段树维护,便要查询线段树历史版本前缀和的和。还有个问题是这个修改的位置很难确定。
我们反过来直接维护前缀\(min\)值,转为查询线段树历史版本和
考虑将区间右端点从大到小枚举。当 \(a_i\) 被删去,最后出现位置变成了 \(a_{last_i}\) 时,修改的将会是一段从 \(a_i\) 开始连续的区间。区间右端点线段树上二分得到即可
于是线段树的操作就从区间取 \(min\) 变成了区间覆盖。
线段树历史版本和
对线段树进行一系列修改操作,当前进行到操作 \(m\),求线段树所有历史版本区间和。
...........看崩溃了,先弃坑好了。

浙公网安备 33010602011771号