oi
“最大权闭合子图”
如果割掉正权边,代表选择正权边和对应的负权边不太合适,所以表示不选择正权边
如果割掉负权边,代表宁可付出割负权边的代价也要选择正权边,所以表示选择正权边
最小割就是割一部分正权边、另一部分负权边所付出的代价,即 min(被选择的一部分正权边(点)+ 没选择的另一部分负权边(点))
而最大权闭合子图的权是 : 所有正权边的和 - 未选正权边的和 - 已选负权边的和
因为未选的正权边和已选的负权边是对应的、一起变化的,所以可以把它们放到一个min里面,即 :最大权闭合子图 = 正权边和 - min { 未选正权边和+已选负权边和 }
未被选的正权边和 = 已割的正权边和, 已选的负权边和 = 已割的负权边和 所以 min { 未选正权边和+已选负权边和 } = 最小割
因此 最大权闭合子图 = 正点权和 - 最小割
建图方式:
正权点:源-->该点:权值
负权点:该点-->汇:权值的绝对值
原图的边:正常连,权值inf
“可撤销并查集”
不是什么有趣的科技,但是也不能忘了
大意就是将路径压缩改为启发式合并,然后记录每一次合并的是哪两个点
stack< pair<int, int> > st;
int findf(int x)
{ return x == fa[x] ? x : findf(fa[x]); }
inline void merge(int u, int v)
{
int uf = findf(u), vf = findf(v);
if (uf == vf) return;
if (siz[uf] > siz[vf]) swap(uf, vf), swap(u, v);
fa[uf] = vf; siz[vf] += siz[uf];
// other operation
st.push(make_pair(uf, vf));
}
inline void undo()
{
fa[st.top().first] = st.top().first;
siz[st.top().second] -= siz[st.top().first];
// other operation
st.pop();
}
“对顶堆”
一个小根堆和一个大根堆构成的数据结构,保证小根堆内元素严格大于大根堆内元素。
这样就得到了一个从上到下值严格递增的数据结构。通过控制某个堆的大小(适时将一堆堆顶移至另一堆)可动态维护第k大等信息。
“二分图最大团”
二分图最大团的大小等于二分图补图最大独立集的大小。
“鬼东西”
-Wall -Wno-unknown-pragmas -Wl,-stack=512000000
“lambda表达式”
[capture list] (params list) mutable exception-> return type { function body }
https://www.cnblogs.com/DswCnblog/p/5629165.html
前缀线性基
在记录基的同时,记录基所在的位置。在插入时对于线性基的每一位,
-
若该位没有对应的基,则照常插入
-
若已有基,但已有的基的位置劣于当前插入的内容,那么交换当前插入的东西和已有的基,然后继续进行余下步骤。
这样,可以保证线性基的每一位最靠右。
查询时(以序列的线性基为例),若查询位置在 \(l\) 之后的元素的信息,那么线性基中的某个基能够作出贡献的前提就是,基记录的位置大于 \(l\)。
对于序列而言,前缀线性基可以这样实现:
inline void insert(int v, int id) // 在id这个位置插入值为v的元素
{
for (int i = 20; i >= 0; i--)
if ((v>>i) & 1)
{
if (!base[i]) return pos[i] = id, base[i] = v, void(); // 若没有基则照常
else if (pos[i] < id) swap(pos[i], id), swap(base[i], v); // 有基,但不优,那么替换为插入的东西。注意是swap,而不是直接替换
v ^= base[i];
}
}
inline int query(int id) // 查询位置在id之后的所有元素的最大异或值
{
int res = 0;
for (int i = 20; i >= 0; i--)
if (pos[i] >= id && (res^base[i]) > res) // 能够产生贡献的前提是,这个基出现的位置在id之后
res ^= base[i];
return res;
}
对于树上前缀线性基,将记录位置改为记录深度即可。区间前缀线性基可参考CF1100F,树上前缀线性基可参考P3292.
垃圾模数时的组合数
将模数分解质因数,分解为 \(p_1,p_2,...,p_k\) 。
普通的阶乘在递推第 \(i\) 项时会乘以 \(i\);这里乘以 \(i\) 除去模数所有质因子后的值,并记录 \(cnt\_pre_{i,j}\) 表示在 \(i\) 的阶乘中出现了 \(cnt\) 次模数的第 \(j\) 个质因子。
这样可以保证新的意义下的阶乘与模数互质,那么就可以保证存在逆元。逆元可以用欧拉公式求解。
btw,可以看出因为素数的欧拉函数值为素数-1,所以费马小定理是欧拉公式的特殊形式。
那么 \(a^{\varphi(p)-1}\) 就是 \(a\) 的逆元。
最后计算时将约去的质因子与结果相乘,即可得到答案。
在计算 \(\binom{n}{m}\) 时,第 \(i\) 个质因子的出现次数即为 \(cnt\_pre_{n,i} - cnt\_pre_{m,i} - cnt\_pre_{n-m,i}\)
inline void init(int n = 100000)
{
fac[0] = fac[1] = ifac[0] = ifac[1] = 1;
int phi = MOD, tmp = MOD;
for (int x = 2; x*x <= tmp; x++)
if (tmp % x == 0)
{
phi = phi / x * (x-1); pri[++tot] = x;
while (tmp % x == 0) tmp /= x;
}
if (tmp > 1) phi = phi / tmp * (tmp-1), pri[++tot] = tmp;
for (int i = 2; i <= n; i++)
{
tmp = i;
for (int j = 1; j <= tot; j++)
{
pre_cnt[i][j] = pre_cnt[i-1][j];
while (tmp % pri[j] == 0)
pre_cnt[i][j]++, tmp /= pri[j];
}
fac[i] = fac[i-1] * tmp % MOD;
ifac[i] = fast_power(fac[i], phi-1);
}
}
inline ll C(int n, int m)
{
if (n < m || m < 0 || n < 0) return 0;
ll res = fac[n] * ifac[m] % MOD * ifac[n-m] % MOD;
for (int i = 1; i <= tot; i++)
(res *= fast_power(pri[i], pre_cnt[n][i]-pre_cnt[m][i]-pre_cnt[n-m][i])) %= MOD;
return res;
}
c++11 function
并查集的例子:
function<int(int)> findf = [&] (int x)
{ return x == fa[x] ? x : fa[x] = findf(fa[x]); };
广义圆方树与狭义圆方树
广义:点双,每两个点也看成一坨树
void tarjan(int u)
{
dfn[u] = low[u] = ++tim; stk[++top] = u;
for (int v : G[u])
{
if (!dfn[v])
{
tarjan(v); low[u] = min(low[u], low[v]);
if (low[v] >= dfn[u])
{
// if (stk[top] == v) { top--; continue; } in case you're doing e-scc
int now;
do
{
now = stk[top]; top--;
// "now" is part of this shit
}
while (now != y);
// here, don't you forget "u" is also part of this shit
}
}
else low[u] = min(low[u], dfn[v]);
}
}
狭义圆方树:边双,也是看起来比较自然的圆方树。把大小为2的双连通分量判掉就行。代码同上,但要在 low[v] >= dfn[u] 后面判掉 stk[top] == y 的情况。

浙公网安备 33010602011771号