信友队 2024 暑假集训 · 壹

Day 0

Day 1

早上入营测,T1 脑子糊涂写错细节。总共挂了 \(100^+\) 分(only \(10\)pts)

例题

  • P1842

    邻项交换法考虑。

  • P2949

    反悔贪心。

  • P1080

    邻项交换。

  • P1016

    如果不太好确定当前干嘛,适合反悔。

    增加卖油操作,可以把比当前加油站贵的,剩下的油原价卖了(不用管卖给谁)。路上优先烧便宜的油。

  • AcWing 115

    \(n \le 10^5\)

课后作业

  • P5817

    Solution

  • 细胞分裂

    简单题。

  • 买彩票

    • 思维别固定了,灵活些。有二者关系的别随意钦定。

Day 2

例题

模拟

构造

Day 3

lxl 毒瘤!!!

写点新奇的。

笛卡尔树

建树

找出区间最大值,把区间分为两段。每段的最大值作为父亲,向后二者连边。

应用

所有区间 + 区间 \(\max\)

注意到笛卡尔树的每个结点的左右子树合起来就是其为区间 \(\max\) 的区间。(左子树一个左端点,右子树)

所以我们可以以此应用笛卡尔树。

  • 写题时补上例题

Day 4

@p_b_p_b !!!

搜索。确定搜索顺序,找到合理有用剪枝。

随堂练习 T2 黑,调了好久终于过了

Day 5

MST.

例题

  • CF609E

    输出每条边必选时的 MST 权值。

  • ABC282E

    简单题。

  • CF1857G

    从小到大加边,像 Kruskal 一样维护连通块。连一条边的贡献是左右连通块大小与可选边权数之积。这个 trick 似乎很有用。

  • #3004. Inheritance

    • 给一个边权互不相同的图,进行 \(K\) 次删去最大生成森林的操作。对于每条边,求出它在第几轮被删除。

    • 图分为 \(K\) 层,\(K\)试图从大到小加边。每层图用并查集维护连通性。

    • 判断某条边在第几层加的时候,二分。

    • 这是因为,两个点如果在第 \(A\) 张图联通,在 \(A-1\) 层也是联通的。

  • 「NOI2018」归程

  • CF1706E

    • 考虑 \(2\) 个点时的答案:MST 上的路径最大值。

    • 推广:相邻点之间的路径最大值 的最大值。

    • 直接预处理,线段树维护即可。

  • CF888G

    • 有异或,想到 Trie

    • 那么我们就是在 Trie 上模拟 Kruskal 的选边过程。

    • 肯定优先选择 \(\operatorname{LCA}(u, v)\) 深度大的连边。如果要合并 Trie 上 \(2\) 棵子树,那就左右子树各选一个,使其异或和最小。可以启发式合并。

  • 不知名题目

    • 一个长度为 \(N\) 的 01 串 \(S\),有 \(M\) 种操作 \((C_i, L_i, R_i)\),表示可以用 \(C_i\) 的代价把 \(S_l, \dots, S_r\) 取反。求可以把 \(S\) 修改为任意长为 \(N\) 的 01 串的最小代价。

    • CF1120D 很像。

    • 先把序列取反改成差分数组两点修改。

    • 然后就是 1120D 的套路:\(L\)\(R+1\) 连权值为 \(C\) 的边。跑 MST。

中午

和 xhx,hyn 和 wjk 在寝室狂玩。

玩什么?

——行李箱“冰壶”,启动!

Day 6 休息日

把昨天所有的题 AK 了。

CF888G 可真牛啊,写发题解。

Day 7

例题

Topologic Sorting

  • P1954 「NOI2010」航空管制

    时光倒流,最大难点。

  • P6381 Odyssey

    • \(ab=c^k\) 等价于 \(s_a + s_b \equiv 0 (\bmod \ k)\)

    • 我们就用这个性质寻找一条边可以匹配的边,用 map,Hash table 这类东西存即可。拓扑。

  • P3243

Hash

讲解
  • 字符串 Hash

  • 集合 Hash

    • 常用写法:\(H(A) = \sum_{x \in A} g(x) \bmod 2^{64}\)

    • 其中 \(g(x)\) 是个随机的函数。当然 \(x\) 一样时 \(g(x)\) 也是一样的。

    • 给个例子(只适用于 \(x\) 的值域比较小的情况):

    struct Hash {
    	vector <ull> g;
    	Hash(int n) : g(n) {
    		static mt19937_64 rnd(809729);
    		for (ull& i : g)
    			i = rnd();
    	}
    	ull operator()(const vi a) const {
    		ull ret(0);
    		for (int& i : a)
    			ret += g[i];
    //		for (int i = 0; i < s.size(); i++)
    //			ret += g[i] * a[i];
    // 注释的写法有问题:
    // 1. 集合的元素是无序的
    // 2. 乘法一个很不好的地方就是更容易产生偶数
    		return ret;
    	}
    } hs;
    // 调用 hs(vector < int >)
    
    • Another Example(\(x\) 可以随便)
    const ull mask = std::chrono::steady_clock::now().time_since_epoch().count();
    
    ull shift(ull x) { // another g(x)
    	x ^= mask;
    	x ^= x << 13;
    	x ^= x >> 7;
    	x ^= x << 17;
    	x ^= mask;
    	return x;
    }
    
  • 树哈希

    • \(f(u) = 1 + \sum_{v \in subtree_u} g(f(v))\)

    • dmy 的 blog

  • 最小表示法什么的,没听懂

例题
  • 「BJOI2015」树的同构

    • 树哈希模板题。
  • 「CTSC2014」企鹅 QQ

    • 简单题
  • P2757 国集 等差子序列 / #6287. 诗歌

    • 太妙了!

    • 首先我们转化一下,找 \(len = 3\) 的等差数列即可。

    • 然后,如果 \(a_i\) 是中间那个,则需要 \(\exist k\),满足 \(a_i + k\)\(a_i - k\) 在排列中,在 \(a_i\) 的两侧。

    • 这样,我们就需要检测是否存在 \(k\),或什么时候 \(k\) 不存在。首先扫描 \(A_1 \sim A_n\),维护 \(B\)\(B_x=1\) 表示在排列中 \(x\)\(A_i\) 的左边,\(B_x = 0\) 反之。如果 \(B\)\(A_i\) 为中心回文,则 \(k\) 不存在。

  • 牛客 15253

    • simple, set 常数比 map 小很多,尽量不用 map

    • 用了 mapunordered_map,被迫卡了几小时的常(换 set 才过)。

Day 8

啊?时间过半了?

例题

强连通分量

  • 缩点模板题

  • 又一个缩点模板题

割点与桥

讲无聊内容好多。。。

圆方树也可以建单向边,就是 割点 - 方点,方点 - 另外点。

  • CF1000E

    • 板子题,边双缩点找直径。
  • P2860 06Jan G

    • 不停边双缩点,缩点后建虚树,虚树的直径两端连边。(这是构造方法)

    • 答案是缩点后叶子的数量除以 \(2\) 向上取整。

  • CF487E

    • 圆方树 + 树剖,板子题。

随堂练习全做过,2min AK。

课后练习

Day 9

讲课内容

  • 质数数量规模 \(\pi(n) \approx n / \ln n\)

  • 唯一分解定理

    \[n = \prod p_i ^ {s_i} \]

    且分解至多 \(O(\log n)\) 项。

    • 因数个数

      \[\prod_i (s_i + 1) \]

  • gcd, lcm

    本质就是唯一分解的指数取 \(\max\)\(\min\)。因此 \((n, m) \times [n, m] = nm\)

  • 筛子

    • 埃氏筛

      时间复杂度 \(O(n \ln \ln n)\),证明略,希望你记住了。

    • 欧拉筛

      运行效率和埃氏筛差不多,但用处多。

  • 数论函数,积性函数

    • \(\mu(n), \varphi(n), \epsilon(n) = 1, id(n) = n, d(n)\)

    • 积性函数,都可以用筛法计算。

    • 对于完全积性函数,\(f(p^2) = f(p)^2\)

    • 线性筛求 \(\varphi\), \(\mu\)

    • \(\sum_{d | n} \varphi(d) = n\),由此我们可以得到一个 \(O(n \log n)\) 的简单方法:

      for (int i = 1; i <= n; i++)
      	phi[i] = i;
      for (int i = 1; i <= n; i++)
      	for (int j = 2 * i; j <= n; j += i)
      		f[j] -= f[i];
      
    • 一些公式

      \[\because \varphi(p^k) = p^k - p^{k - 1} \]

      \[\therefore \varphi(n) = n \prod \frac{p_i - 1}{p_i} \]

      \[\sum_{d | n} \mu(d) = [n = 1] \]

      最后一个式子可以用二项式定理与 \(\mu\) 的定义证明。

例题

  • CF1614D1

    dp, 考虑将 \(a\) 放在开头的最大值,\(dp_1 = n\)

    \[dp_a = \max_{b | a} \{dp_b + (a - b) \times \sum_{i=1}^n [a |a_i]\} \]

    复杂度 \(O(n \ln n)\)

  • P2568

    发现以前交错代码了 qwq

    • 莫比乌斯反演的思路:要变出 \([n = 1]\) 的形式,然后化为 \(\sum_{d|n} \mu(d)\)。化很久几下,答案:

    \[\sum_{p \in P} \sum_{k = 1} \mu(k) \lfloor n / pk \rfloor \lfloor m / pk \rfloor \]

    求的复杂度是调和级数的。

    实际上是推到一半发现了 \(\varphi\),直接随便做。

  • \(\sum_{i = 1}^n i^k\)\(n, k \le 10^7\)

    注意到 \(id_k(n) = n^k\) 是(完全)积性函数,直接筛就行。

  • 区间加区间 \(\gcd\) 查询

    • 首先差分,由于 \(\gcd(a, b) = \gcd(a, b - a)\),所以差分序列的区间 \(\gcd\) 与原序列是一样的。

    • 然后可以考虑单点修改区间查询,\(O(n \log n \log a)\)也许不能通过?

    • 然而过了???

    • 注意到,区间 \(\gcd\) 暴力求是 \(O(n + \log a)\) 的。所以询问与 pushup 就是求 \(\log n\) 的数的 \(\gcd\),复杂度 \(O(\log n + \log a)\)。总复杂度 \(O(m(\log n + \log a))\)

    • 附注:ST 表预处理 \(\gcd\) 也是 \(O(n(\log n + \log a))\) 的。

电脑坏了,cnblogs 密码忘记了 qwq

07/17

学校什么 ** 电脑,关机后上午笔记丢了。

同时讲了 07/17 和 07/18 的内容,数论讲完了。

稍微补一点笔记,直接内容例题结合了。

同余最短路

我是大鸽子。

例题

  • 跳楼机

  • Roger 买鸭子

07/18 模拟赛

%UR,A 没切,其余 \(3\) 题全暴力,可以退役惹。

总分 \(80\),至少比上次高 ,还是宿舍 rk. 1 呢

题解

A. WTP 的大洗牌

题意:求 \(x,y\),使 \(x^2 + y^2 \equiv \prod_{i = 1}^n a_i^2 + b_i^2 (\bmod 2^{64})\)

\(n \le 10^5, x, y, \le 2^{64}\)

\(10000ms\) 的时限纯诈骗,正解是 \({O(n)}\) 的。

我们可以采用推广法其实是看到 \(n=2\) 的部分分。先考虑 \(n=2\) 的情况。

\[\begin{aligned} (a^2+b^2)(c^2 + d^2) &= (ac)^2+(bd)^2+(bc)^2+(ad)^2 \\ &= (ac + bd)^2+(ad-cb)^2 \end{aligned}\]

根据这个,我们可以轻松构造出一组解,而会了 \(n=2\) 我们很快能推广到 \(n\) 的普遍情况。

Code
#include <bits/stdc++.h>
using namespace std;

using ull = unsigned long long;
#define L(i, j, k) for (int i = j; i <= int(k); i++)
#define R(i, j, k) for (int i = j; i >= int(k); i--)
#define qio() ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)

const int N = 5e5 + 5;

int n;
ull a[N], b[N];

int main() {
	qio();
	cin >> n;
	L(i, 1, n)
		cin >> a[i];
	L(i, 1, n)
		cin >> b[i];
	ull a1 = a[1], b1 = b[1];
	L(i, 2, n) {
		ull aa = a1, bb = b1;
		a1 = aa * a[i] + bb * b[i];
		b1 = aa * b[i] - bb * a[i];
	}
	return cout << a1 << ' ' << b1 << '\n', 0;
}

B. 第一题

给一个字符串 \(s\),对于 \(i = 1 \dots |s|\),进行如下操作:

  • 如果 \(t\) 是空串,则 \(t = s_is_i\)

  • 否则,交换 \(t_2, t_3\)\(t_4, t_5\)……并将 \(s_i\) 插入至 \(t\) 的第二、倒数第二位。

每次操作后,求 \(t\) 的所有前缀偶回文串的哈希值

\[\sum_{i = 1}^{|s|} (\operatorname{ASCLL}(t_i) - \operatorname{ASCLL}(a))131^{|s|-i} \bmod 2^{64} \]

强制在线。

首先,这个操作很神秘,我们不知道它在做什么,自然想不出它的性质。因此我们先模拟一下这个操作。

直接拿几个字符做例子太累了,我们直接用 12345678

模拟后,我们可以发现,字符的顺序变成了 1827364554637281。把这个串写出来后我们自然发现:

  • 奇数位是原串,偶数位是原串的反串。

这个性质很特殊,我们继续观察。

一个字符串是回文串,为什么是?我们考虑偶回文串。

182736 为例,如果它是回文串,则 123 = 678。因此我们又发现,偶回文串一定是原串的 Border。

于是我们就可以这样:维护 Hash,再求出后缀树上到根的 Hash 和。(什么玩意儿是人话吗,看 Code 吧)

注意细节,Hash 时用到了 \(bp_{2 |s| - 2}\),所以数组要开大。(本地测下极限数据即可发现)

Code
#include <bits/stdc++.h>
using namespace std;

using ll = long long;
using vi = vector < int >;
using ull = unsigned long long;
#define pb emplace_back
#define L(i, j, k) for (int i = j; i <= int(k); i++)
#define R(i, j, k) for (int i = j; i >= int(k); i--)
#define qio() ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)

const int N = 5e6 + 5;
const int base = 131;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fll;

string s;
int k, nxt[N];
ull bp[N << 1], ans[N]; // 数组忘开大,爆零两行泪

void output() {
	ull out(0);
	L(i, 1, s.size() - 1) {
		out ^= ans[i];
		if (i % k == 0) {
			cout << out << '\n';
			out = 0;
		}
	}
}
char online(char ch, ull last) {
	return char(last % 26 + (ch - 'a')) % 26 + 'a';
}
int val(char ch) {
	return ch - 'a';
}
int solve() {
	bp[0] = 1;
	L(i, 1, 2 * s.size() - 1) // 我们之后用到了 bp[2 * i - 2]
		bp[i] = bp[i - 1] * base;
	
	ull hs(0), conv_hs(0);
	for (int i = 1, k = 0; i < (int)s.size(); i++) {
		s[i] = online(s[i], ans[i - 1]);
		
		hs *= base, hs += val(s[i]), hs *= base; // Calc Hash
		conv_hs += val(s[i]) * bp[2 * i - 2];
		
//		printf("%llu (i = %d)\n", hs + conv_hs, i);
		
		if (i == 1) { // Special Judge
			ans[i] = hs + conv_hs;
			continue;
		}
		
		while (k && s[i] != s[k + 1]) // KMP
			k = nxt[k];
		nxt[i] = k += s[k + 1] == s[i];
		
		ans[i] = ans[nxt[i]] + hs + conv_hs; // Calc ans
	}
	
	return output(), 0;
}

int main() {
	freopen("easy.in", "r", stdin);
	freopen("easy.out", "w", stdout);
	qio();
	
	cin >> s >> k;
	s = " " + s;
	
	return solve();
}

C. WTP 的通缉

给一棵有边权的树,\(q\) 次询问求编号\([l_1,r_1], [l_2, r_2]\) 之间的点各选一个,求最大距离。

连续区间,想到线段树选点集里最大距离,就是广义直径

所以这题其实是很板的一个题,\([l, r]\) 的直径可以线段树维护,因为两个点集合并后,直径的端点一定是原直径的端点。于是我们进行一个线段树的查询即可。

Code
#include <bits/stdc++.h>
using namespace std;

using ll = long long;
using ull = unsigned long long;
#define pb emplace_back
#define me(a, b) memset(a, b, sizeof(a))
#define L(i, j, k) for (int i = j; i <= int(k); i++)
#define R(i, j, k) for (int i = j; i >= int(k); i--)
namespace io {
	int read() {
		int res(0), f(1);
		char ch(getchar());
		while (ch < '0' || ch > '9') {
			if (ch == '-')
				f = -1;
			ch = getchar();
		}
		while (ch >= '0' && ch <= '9') {
			res = res * 10 + (ch ^ '0');
			ch = getchar();
		}
		return res * f;
	}
} 
using io::read;

const int N = 1e5 + 5;
const int LG = 18;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fll;

struct edge {
	int v, w;
};
int n, m;
ve G[N];

namespace LCA {
	int dfc, dfn[N], dep[N], rmq[LG][N];
	ll edis[N];
	
	int get(int x, int y) {
		return dfn[x] < dfn[y] ? x : y;
	}
	void dfs(int u, int fa) {
		rmq[0][dfn[u] = ++dfc] = fa;
		for (auto e : G[u])
			if (e.v != fa) {
				edis[e.v] = edis[u] + e.w;
				dep[e.v] = dep[u] + 1;
				dfs(e.v, u);
			}
	}
	void init() {
		dfs(1, 0);
		for (int i = 1; i <= __lg(n); i++)
			for (int j = 1; j + (1 << i) - 1 <= n; j++)
				rmq[i][j] = get(rmq[i - 1][j], rmq[i - 1][j + (1 <<(i - 1))]);
	}
	int lca(int u, int v) {
		if (u == v)
			return u;
		if ((u = dfn[u]) > (v = dfn[v]))
			swap(u, v);
		int d = __lg(v - u++);
		return get(rmq[d][u], rmq[d][v - (1 << d) + 1]);
	}
	ll dis(int u, int v) {
		if (!u || !v)
			return -INF;
		int l = lca(u, v);
		return edis[u] + edis[v] - 2 * edis[l];
	}
} using LCA::dis;

struct chain {
	int u, v;
	chain() {
		u = v = 0;
	}
	chain(int x, int y) {
		u = x, v = y;
	}
};

chain merge(chain a, chain b) {
	int u1 = a.u, v1 = a.v, u2 = b.u, v2 = b.v;
	
	if (!u1 || !v1)
		return b;
	if (!u2 || !v2)
		return a;
	
	ll mx = max({dis(u1, v1), dis(u1, v2), dis(u2, v1), dis(u2, v2), dis(u1, u2), dis(v1, v2)});
	if (mx == dis(u1, v1))
		return chain(u1, v1);
	if (mx == dis(u1, v2))
		return chain(u1, v2);
	if (mx == dis(u2, v1))
		return chain(u2, v1);
	if (mx == dis(u2, v2))
		return chain(u2, v2);
	if (mx == dis(v1, v2))
		return chain(v1, v2);
	if (mx == dis(u1, u2))
		return chain(u1, u2);
	return chain(0, 0);
}

struct sgt {
	chain ch[N << 2];
	
	sgt() {}
	
	#define ls(u) (u << 1)
	#define rs(u) (u << 1 | 1)
	void pushup(int u) {
		ch[u] = merge(ch[ls(u)], ch[rs(u)]);
	}
	void build(int s, int t, int u) {
		if (s == t)
			return (void)(ch[u] = chain(s, t));
		int mid = (s + t) >> 1;
		build(s, mid, ls(u));
		build(mid + 1, t, rs(u));
		pushup(u);
	}
	chain query(int s, int t, int l, int r, int u) {
		if (l <= s && t <= r) 
			return ch[u];
		int mid = (s + t) >> 1;
		chain ret = chain(0, 0);
		if (mid >= l)
			ret = query(s, mid, l, r, ls(u));
		if (mid < r)
			ret = merge(ret, query(mid + 1, t, l, r, rs(u)));
		return ret;
	}
} tr;

int main() {
	n = read(), m = read();
	for (int i = 1, u, v, w; i < n; i++) {
		u = read(), v = read(), w = read();
		G[u].push_back({v, w});
		G[v].push_back({u, w});
	}
	
	LCA::init();
	tr.build(1, n, 1);
	
	while (m--) {
		int l1 = read(), r1 = read(), l2 = read(), r2 = read();
		auto rec1 = tr.query(1, n, l1, r1, 1), 
			 rec2 = tr.query(1, n, l2, r2, 1);
		int u1 = rec1.u, v1 = rec1.v,
			u2 = rec2.u, v2 = rec2.v;
		printf("%lld\n", max({dis(u1, u2), dis(u1, v2), dis(v1, u2), dis(v1, v2)}));
	}

	return 0;
}

D. 蹦蹦炸弹

一张图,\(m\) 次连边,形式为 \(x, y, l, w\),表示对于 \(i \in Z^+ \cap [0, l)\)\(x + i\)\(y + i\) 之间连一条边权为 \(w\) 的边。求 MST。

其实这题是原:Range Parallel Unionfind

老师:我一眼就切了。可是……

我们考虑 sort 后加边,并查集维护连通性(因为 Kruskal 就是这么做的),但如何并查集?

其实区间连边,让我们想到了拆区间操作,线段树优化建图不也是这样吗?

于是我们可以维护 \(O(\log n)\) 层并查集,第 \(i\) 层并查集中,\(u,v\) 连通表示 \([u, u + 2^i-1),[v, v + 2^i - 1)\) 连通。

高层的并查集要把连通性下推到低层,看起来很暴力,但很显然一个并查集最多连边 \(O(n)\) 次,因此总复杂度是 \(O(n \log n)\)

Code
#include <bits/stdc++.h>
using namespace std;

using LL = __int128;
using ll = long long;
using ldb = long double;
using vi = vector < int >;
using ull = unsigned long long;
#define pb emplace_back
#define ve vector < edge >
#define me(a, b) memset(a, b, sizeof(a))
#define L(i, j, k) for (int i = j; i <= int(k); i++)
#define R(i, j, k) for (int i = j; i >= int(k); i--)
#define qio() ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
namespace io {
	int read() {
		int res(0), f(1);
		char ch(getchar());
		while (ch < '0' || ch > '9') {
			if (ch == '-')
				f = -1;
			ch = getchar();
		}
		while (ch >= '0' && ch <= '9') {
			res = res * 10 + (ch ^ '0');
			ch = getchar();
		}
		return res * f;
	}
} 
using io::read;

namespace Dbg {
	template < class T >
	void dbg(char *f, T t) {
		cerr << f << '=' << t << endl;
	}
	template < class T, class ...Ar >
	void dbg(char *f, T x, Ar... y) {
		while (*f != ',')
			cerr << *f++;
		cerr << '=' << x << ',';
		dbg(f + 1, y...);
	}
}
#define gdb(...) cerr << "gdb in line " <<__LINE__ << ": ", Dbg::dbg((char*)#__VA_ARGS__,__VA_ARGS__);

const int N = 1e5 + 5;
const int M = 5e5 + 5;
const int LG = 18;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fll;

struct connect {
	int x, y, l, w;
	void input() {
		x = read(), y = read(), l = read(), w = read();
	}
} E[M];
int n, m;

struct dsu {
	vi fa;

	dsu() {}

	void init(int n) {
		fa.reserve(n + 1);
		for (int i = 1; i <= n; i++)
			fa[i] = i;
	}
	int find(int x) {
		return x == fa[x] ? x : fa[x] = find(fa[x]);
	}
	bool unite(int x, int y) {
		if ((x = find(x)) == (y = find(y)))
			return 0;
		return fa[x] = y, 1;
	}
};

int main() {
	n = read(), m = read();
	L(i, 1, m)
		E[i].input();

	sort(E + 1, E + m + 1, [](connect a, connect b) {
		return a.w < b.w;
	});

	ll ans = 0;
	vector < dsu > G(__lg(n) + 1);
	for (int i = 0; i <= __lg(n); i++)
		G[i].init(n);

	function < void(int, int, int, int) > merge = [&](int x, int y, int dep, int w) {
//		printf("merge(%d, %d, %d, %d)\n", x, y, dep, w);
		if (!G[dep].unite(x, y))
			return;
		if (!dep) {
			ans += w;
			return;
		}
		merge(x, y, dep - 1, w);
		merge(x + (1 << dep - 1), y + (1 << dep - 1), dep - 1, w);
	};

	L(i, 1, m) {
		auto e = E[i];
		int x = e.x, y = e.y, l = e.l, w = e.w;

		for (int bit = 0; l; l >>= 1, bit++)
			if (l & 1) {
				merge(x, y, bit, w);
				x += 1 << bit, y += 1 << bit;
			}
	}
	return printf("%lld\n", ans), 0;
}

比赛总结

反正信友队那里写了,直接 copy 过来。

考试过程

我用约 15min 通读了题目

发现 D 是最小生成树,没细看,似乎可以线段树优化建图

然后开 A,瞪了几下没什么结果,也没有推式子、找性质,突然看到
A 的时限是 10000ms,顿时很惊讶,以为正解是暴力

大约 45min 的时候给 A 写完了暴力(完全错掉)

之后观察了下别的题,用 30min 先写了 C 的 30pts 暴力

之后我错认为我发现了 D 的并查集正解(假了,和正解还有差距)
于是开始写。写了几下发现大样例过不去,于是花了 30min 对拍
对拍过程中发现题读错,假完了,只好交一发 40pts 的暴力.这时 10:48

现在唯一能拿的是 B 的部分分,我写了个巨难写的暴力(真的比正解还难写),11:40 写完,发现爆零了。

最后我发现我代码交错了,还有 freopen 没删的情况,救了
70pts

知识缺漏

  • 我觉得我好想只用了普及组的知识点

  • 中间几个问题是什么鬼

    • 我的代码能力似乎有很大进步

    • 但思维能力有待很大提升

  • 知识缺漏

    • 不会数学推导,A 都推不出

    • 没有熟练掌握算法的运用

策略缺失

好的:

  • 我打了很多暴力

  • 我发现正解写假后立刻写了对拍与暴力

不好的:

  • 我没看 A 的部分分,根本没发现 \(n = 2\) 的提示性,直接写了假的暴力

  • 写 D 前我没有验证做法正确性,导致浪费 1h+ 用于写假做法与对拍

  • B 我没有模拟操作,对操作的性质一无所知

改进方案

  • 要多做比赛,比赛时算法不会给出 tag

  • 上课要多想,比赛时的做法都得自己想

  • 多对题目做总结

  • 作业要线下先验证一遍

    • 测极限数据

    • 样例过了 \(\neq\) AC

07/20

上课内容

组合数学

  • 下降幂 \(n^{\underline{m}}=A_n^m\)

  • \(\sum_{i = 0}^m \binom{i}{n} =\binom{m+1}{n+1}\),这个东西好像是可以用杨辉三角证明。

  • \(\sum_{i = 0}^k \binom{n}{i} \binom{m}{k - i} = \binom{n + m}{k}\),范德蒙德卷积,可以用二项式定理证明。

  • \(\binom{n}{r} \binom{r}{k} = \binom{n}{k} \binom{n - k}{r - k}\),组合意义:选 \(r\) 个人,\(r\) 个人里选 \(k\) 个发奖。后面是先发奖,再选炮灰。

  • 二项式定理。

  • 二项式反演。

    • 如果 \(g_n = \sum_{i = 0} ^ n \binom{n}{i} f_i\),则

    \[f_n = \sum_{i = 0}^n (-1)^{n - i} \binom{n}{i} g_i \]

    可以带入啊算个半天,最后得出来了。

  • 错排

    • 加入,交换,\(n - 2\) 条限制

    • 加入,交换,加上 \(i\) 不在 \(n\) 的限制,共 \(n - 1\) 条限制

    • 加起来,\(D_n = (n - 1)(D_{n - 1} + D_{n - 2})\)

    • 或者还有 OI-Wiki 的理解方式

    • 还有一个式子:\(D_n = \lfloor \frac{n!}{e} \rfloor\)

线性代数初步

  • 矩阵的一些运算

    • 原来 \(\det\) 的定义是凑出来的 😃

    • 注意矩阵乘法无交换律,但有结合律,就可以用线段树搞动态 DP 之类的东西,当然我不会

  • 矩阵幂,线性递推

例题

  • CF1312D Count the Arrays

    \[2^{n - 3} \times \binom{m}{n-1} \times (n - 2) \]

    式子做的时候重新推。

  • NOIP2011

  • HNOI2012 排队

    把老师当 boys - 把老师绑一块成一个 boy

  • P6601 「EZEC-2」机器

    推式子。

  • 矩阵乘法水题

  • 矩阵乘法改写再矩阵快速幂水题

  • 类线性递推

    • 添加一些常数或者什么项。
  • 魔法值

    就是以前秦老师讲过的 \(O(q n^3 \log a)\) 用矩阵乘法结合律优化为 \(O(q n^2 \log a)\) 的题,记得吧!

    预处理 \(X^{2^k}\),从右到左乘一遍,\(O(n^3 \log a + q n^2 \log a)\)预处理平衡复杂度。

07/20-2

怎么还上课。

Gauss Elimination

  • 高斯消元

  • 逆矩阵

  • 行列式

  • 一个图,每次可以选一条边,将端点的 \(0/1\) 状态取反。开始时全 \(0\),问能否全 \(1\)

    • 相邻异或和 \(=1\),解方程,bitset 优化。
  • 线性基

    • ABC141F

      \(X_r \oplus X_b\) 是一定的,如果总体异或和的某一位是 \(1\),置零,因为不论哪边是 \(1/0\) 都没关系,对于 \(0\) 的位,线性基。

  • 高斯消元与 DP

07/21

模拟赛,\(100 / 100 + 100 / 100 + 20 / 100 + 0 / 100 = 220 / 400\), rank. 1,这把我开了。

07/22

  • 好些的整数三分

    while (r - l > 3) {
    	int lmid = l + (r - l) / 3,
    	    rmid = r - (r - l) / 3;
    	if (f(lmid) < f(rmid))
    		l = lmid;
    	else
    		r = rmid;
    }
    auto top = f(l);
    while (f(l + 1) > f(l))
    	l++;
    

例题

  • HNOI2009 最小圈

    • \(w - mid\) 最小环,负环判断。
  • CF1355E

    三个操作,知道总代价没用。

    二分好像没相关的单调性,因此三分最终所有楼的高度 \(H\) 即可。

  • CF526F

    首先我们可以简单转化为求多少个 \((i, j)\) 满足 \(\max\{y_i \dots y_j\} - \min\{y_i \dots y_j\} = j - i\)

    分治,递归上来时记录前后缀极值。合并出的贡献是 \(\max (suf_i, pre_j) - \min(suf_i, pre_j) = j - i\)。我们拆 \(\max \min\) 并分讨即可。一种情况直接做,另一种情况双指针。

  • AGC006D

07/23

模拟赛,寄大了,只有 rk.8。

总结

这把又打出了我 OI 里的一堆问题,当然不妨先写一点点优点:

  • 通读了题目

哈哈,竟然想不出别的 💔

首先策略上自带极大的问题,我用大半场的时间死磕 B 题,导致 C 没认真看,最后 B 也没想出来。

其次,在思考方向方法上也有巨大问题——可怕的 dfs 式想题。在思考 B 一个多小时,搞出了好几个假做法后,任然没有一个可行的解时,我任然在考虑 B 如何用图论做(这是我想不出来的,最大团是 NPC)。如果我第一次假后就换成数论 + DP 版的思考方向应该能拿很多分。部分分也提示了这一点,但我没有醒悟过来。

最后,我不怎么会打暴力,如果是指数级的暴力是可以剪枝的,然而我没有。这就导致 B 题分数直接垫底。

其实这么看,上一场是很狗运的。我一遍想出了 AB 的做法,并且一遍写对,实在是遇到了喜欢的题。

posted @   sihiazi  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示