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



前缀线性基

P3292 CF1100F

在记录基的同时,记录基所在的位置。在插入时对于线性基的每一位,

  • 若该位没有对应的基,则照常插入

  • 若已有基,但已有的基的位置劣于当前插入的内容,那么交换当前插入的东西和已有的基,然后继续进行余下步骤。

这样,可以保证线性基的每一位最靠右。

查询时(以序列的线性基为例),若查询位置在 \(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\) 个质因子。

这样可以保证新的意义下的阶乘与模数互质,那么就可以保证存在逆元。逆元可以用欧拉公式求解。

\[a^{\varphi(p)} \equiv 1 \pmod{p} \]

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 的情况。

posted @ 2020-10-03 15:56  MikeDuke  阅读(264)  评论(0)    收藏  举报