杂题选做

ARC204A Use Udon Coupon

转化为求一个【遍历顺序】。
记最终的序列形如,减一个 \(a\) 值,和 \(0\) 取 max,加一个 \(b\) 值,和 \(0\) 取 max,……
然后注意到这个 \(C\) 干的事情就是,操作完就是最大的后缀和。
于是对操作序列计数,倒着计数,时时刻刻保证后缀和 \(\le R\) 就行。
然后你如果状态设为 \(f_{i, j}\) 的话是可以根据 \(i,j\) 直接算出后缀和的。\(work(R) - work(L-1)\)

ARC204B Sort Permutation

差一点就想到了啊!!不知道这个东西典不典。

哎这个要求“最少的操作次数下,最大得分”,先考虑这个最少的操作次数是什么。
注意到。注意到每个置换环是独立的!然后考虑交换在干什么。
实际上就是把一个置换环拆成两个。target 是变成 \(nk\) 个长为 \(1\) 的置换环。然后每次保证在同一个置换环中取两个点就是最优的。

那么就是选出来一个最大生成树,然后要保证不存在两个边“相交”(物理意义上的)。
如果把这个环拍到一个序列上,容易想到区间 DP,枚举 \(l\) 最右边一条出边连到谁,普通写法是 \(O(n^3k^3)\) 的。止步于此。

然后这里注意到瓶颈在于区间 DP \(f_{l, r}\) 的时候枚举转移点。这里分成两类。

  • 存在一条 \(l\) 的出边权值为 \(1\):只有 \(O(k)\) 个。
  • 存在一条 \(r\) 的出边权值为 \(1\):也只有 \(O(k)\) 个。
  • 其他情况:显然不如直接选 \(l \leftrightarrow r\)

那么总复杂度 \(O(n^2k^3)\)

ARC172D Distance Ranking

问号题。马队伟大。

\[\begin{aligned} dis(i, j)^2 &=\sum_{t=1}^n (p_{i, t} - p_{j, t})^2 \\ & = p_{i,i}^2 - 2p_{j,i}p_{i,i}+p_{i,i}^2 - 2p_{j,j}p_{i,j}+p_{i,j}^2+p_{j,i}^2+\sum_{t \ne i, t \ne j}(p_{i, t} - p_{j, t})^2 \end{aligned} \]

这里发现。发现。发现。发现若令 \(p_{i, i}=C=100000000\) 的话那么

\[\begin{aligned} & = 2C^2 - 2(p_{j,i}+p_{i,j})C+p_{i,j}^2+p_{j,i}^2+\sum_{t \ne i, t \ne j}(p_{i, t} - p_{j, t})^2 \end{aligned} \]

这里 \(p_{i,j}\) 的影响很小啊。可以忽略。这就是为什么上一步赋 \(p_{i, i}\) 为无穷。

于是 \((i,j)\) 的偏序关系可以几乎转化为 \(p_{i, j}\) 之间的偏序关系。赋为 \(1, 2, \dots, \frac 12n(n-1)\) 就行。

ARC202B Japanese "Knight's Tour"

挺好的题。哈密瓜。
总的宗旨就是放宽不重的限制,然后放宽后的条件仍然很牛,可以推出来一些充要条件。
具体的,利用的是奇偶性分析,以及排列的和是定值。

ARC201D Match, Mod, Minimize

有道理是紫吗。鉴定为小于 B 题。

\(a\) 升序排序,\(b\) 降序排序,注意到最优解一定是,\(a\) 的前缀依次匹配 \(b\) 的一段后缀,\(a\) 剩下来的部分依次匹配 \(b\) 的剩余部分。

记【这里!】为,上面匹配过程中,钦定的 \(b\) 的后缀的下标 \(i\)
即,\(a[1\dots n-i+1]\) 匹配 \(b[i, n]\)\(a[n-i+2\dots n]\) 匹配 \(b[1, i-1]\)

证明不说。使用调整法是容易看出的。

然后就可以二分答案,我们知道 \(a_i\) 匹配哪些 \(b_j\) 是合法的,然后 \(i\) 对应的合法的【这里!】是 \(O(1)\) 个区间并。
\(n\)\(O(1)\) 组区间求交就可以看到有没有合法的【这里!】。

时间复杂度 \(O(n\log n+n\log V)\)

省选联考 2024 迷宫守卫

嗯。好题。但是疑问为什么一个 D2T1 AH 无人场切。

嗯我一开始做的时候,也是思考了我该怎么确定 \(Q[1]\)。Alice 要使得字典序最大,然后 Bob 是知道 Alice 做了哪些修改的。花费 0 秒想到【二分转 01】的 trick。
具体地,二分一个 \(mid\),记符文大于等于 \(mid\) 的点为黑点,否则为白点。这里合法等价于【???】。(想到这里没有敢想 DP)
直接 DP 一个 \(f_u\) 表示要使得 \(u\) 无论怎么走最后都会走到黑点,最小的花费。转移分类 \(u\) 要不要唤醒就好。

然后我知道了 \(Q[1]\) 之后就不知道往下怎么做了。
哦,我现在知道一定存在一种方案使得 \(Q[1]\) 合法,但是我不知道这种方案是什么。这里注意并不能直接按照 \(f\) 的转移方式确定哪些东西唤醒了,因为可能剩余 money 少的方案,反而【把钱用在了刀刃上】,使得答案更优。

我先沿着 \(u\) 走到 \(Q[1]\),发现删掉这条路径上所有的点后,会分裂成若干个子树。这就是子问题。然后我知道,根更深的子树一定在更浅的之前被遍历,所以我是不是倒着走一遍,递归下去处理问题就好了。
但是这样假如你花费了过多的钱在【最小化 \(Q[2],Q[3]\dots\)】上,导致剩下来的钱不够【维持 \(Q[1]\) 的合法性】是不是就 4 了。

那我怎么办。哦,你说你不能确定方案是什么。但是我知道,想要【维持 \(Q[1]\) 的合法性】,必须要时刻保持我有足够的钱来处理浅一些的子树。这样,我在 \(u\to Q[1]\) 的过程中,到一个点就让 \(K\) 减去一个点的 \(f\) 转移贡献。回溯的时候,再把 \(K\) 加回来,这个时候递归子问题。
这样显然就是正确的了。

程序实现方面,可以设计一个 int 类型的递归函数。返回我出去这个子树的时候,剩下来多少钱。然后有一些细节就是我如何判断要不要唤醒某个结点(就是我在递归兄弟子问题的时候,如果发现我不唤醒不行了,就唤醒,然后重新做这个兄弟的子问题。)

瞎写了一个 \(O(n^3 2^n)\) 的(因为刚才说了要重做兄弟的子问题。带来了又一个 \(n\))。把 DP 的过程写成 lambda 就能过。然后如果预处理 DP 的话可以去掉二分,这样就是 \(O(n^2 2^n)\)。再怎么优化不会。

ICPC 2025 西安 Directed Acyclic Graph

妙妙题。下面的 \(k\) 不是原题中的 \(k\)

要求 \(1\) 到每个点都有路径,然后我们可以自由支配的边数大概是 \(O(m-n)\) 的。不严格等于是因为,可能我们可以用这些自由支配的边,最后在连通性上再利用一次。

想到一个朴素的二进制连边方案。
分两层点,\(a_1\sim a_k, b_1\sim b_k\)\(a_i\) 向所有 \(j\ne i\)\(b_j\) 连边。这样我们可以设 \(S_i\) 为,若第 \(x\) 个二进制为 \(0\) 就把 \(a_x\) 加入 \(S_i\)
不难发现可以构造出来 \(2^k-1\) 个非空的 \(S\)。然后前后缀优化建图可以提升效率。

瓶颈在于额外边的利用。
考虑不再使用二进制,转成 \(t\) 进制。类似地,设 \(a_{x, i}\)\(b_{x,i}\),其中 \(0 \le x < t\)。那么我们最终能表示出来的集合数量是 \((t+1)^k\) 个的。

然后同样地,\(a_{x, i}\) 向所有的 \(b_{y, j}\) 连边,其中 $j\ne i $ 或 \(y < x\)。类似上面的表示方法,拆成 \(t\) 进制写开不难得到 \((t+1)^k\)

然后前后缀优化建图。注意前缀里面,后缀里面各有一个辅助点是不需要的,删掉。
同样,\(a_{x,i}\)\(a_{x-1, i}\) 连边,\(b_{x,i}\)\(b_{x-1,i}\) 连边。可以大幅缩减额外边数。

\(t=3,k=7\) 可以有 \(54\) 个点(不含 \(1\) 号)。把这些点记成 \(2\sim 55\)。然后 \(1\) 号点的连边,只需要连向 \(a_{2,1}\sim a_{2,7}\) 就行,还有连向 \(56\sim 100\) 号点。

总边数恰好为 \(128\)

upw(i, 1, 7) edge.emplace_back(i, i+7), edge.emplace_back(i+7, i+14);
upw(i, 24, 28) edge.emplace_back(i-1, i);
upw(i, 23, 28) edge.emplace_back(i, i-21);
upw(i, 30, 34) edge.emplace_back(i, i-1);
upw(i, 29, 34) edge.emplace_back(i, i-28);

upw(i, 36, 42) {
	edge.emplace_back(i, i+7), edge.emplace_back(i+7, i+14);
	edge.emplace_back(i, i-28), edge.emplace_back(i+7, i-21);
}
upw(i, 50, 55) edge.emplace_back(i, i-27);
upw(i, 51, 56) edge.emplace_back(i, i-22);

edge.emplace_back(100, 22), edge.emplace_back(100, 35);
upw(i, 36, 42) edge.emplace_back(100, i);
upw(i, 57, 99) edge.emplace_back(100, i);

cerr << edge.size() << '\n';
for(auto [u, v] : edge) cout << u % n + 1 << ' ' << v % n + 1 << '\n';

upw(mask, 0, 15999) {
	vector<int> vec;
	upw(i, 1, 7) {
		int x = (mask >> (i-1 << 1)) & 3;
		if(x == 0) vec.push_back(i + 49);
		else if(x == 1) vec.push_back(i + 42);
		else if(x == 2) vec.push_back(i + 35);
	}
	cout << vec.size() << ' ';
	for(auto u : vec) cout << u % n + 1 << ' ';
	cout << '\n';
}

ICPC 2025 沈阳 Play It by Ear

妙妙题。题解咕。

CCPC 2025 济南 Hashing

首先两个树【同构】并不一定同构。有一个点数为 \(15\) 的例子。
考虑这个例子,什么引起了【同构】和同构的区别?

本题中【同构】是用和 size 有关的 hash 判的。于是,考虑把根结点的 hash 值写开,大概是 \(\sum_u\prod_{v\text{ is ancestor of u}}f(sz_v)\)
\(S_u=\{sz_v|v \text{ is ancestor of u}\}\),然后两棵树【同构】就等价于 \(\{S_u|u\in \text{tree}\}\) 相同。

上面那个例子中,引起算重的,由于某一层的若干个结点的【祖先 size 集合】是相同的,那这里就相当于这些点在没有分配自身的 size 之前是等价的。考虑 DP,同时对 \(j\) 个这样的结构 DP。\(f_{i, j}\) 表示目前有 \(j\) 个等价的点,要以它们为根,且每个子树的大小都为 \(i\)

转移。转移很平凡吗为什么我场上认为这个状态设计不那么可做完后跑路了。
暴力枚举某些点,的左右子树分配方案 \((x, i-1-x)\)。(为了转移不算重,讲道理需要再开一维 \(y\) 表示我这一次转移,\(x\le y\),但是没必要,可以滚掉)
同时枚举我这种分配方案应用到了哪些点上。
看似是四次方的,实际上是调和级数 \(O(n^2 \log n)\) 的。注意一些转移,判断边界的时候,考虑除以 \(0\)

CCPC 2025 济南 贵校是构造王国吗 IV

神仙题。右转题解。

先想为什么一定要 \(l=6\)。因为 \(l=5\) 的时候一定是某个 \(a_i\) 为因数,为 \(1\) 就太小了达不到范围。

数形结合:三角剖分。
大胆尝试,\(k,k+1,k+2\),预处理出来上界下界。

CCPC 2025 哈尔滨 F

要求一个本质不同的子序列个数。考虑类似子序列自动机的思想。
首先,这个问题是否和【求本质不同子序列】有所联系?发现不能一一对应,因为有 3 的存在。

但是可以类似地转移。【本质不同子序列】的求解中,我们要让一个子序列被最早地表示,也就是设 \(f_i\) 表示以 \(i\) 为结尾的,转移到某种字符下一个出现的位置。
这里如果暂时没有出现 3 的话,也可以这么转移。但是某段 1/2 末尾的时候,不能直接转移到相同的下一个数,因为中间隔了一段 2/1 没办法消掉。

考虑带上 3,设 \(f_{i,0/1}\) 表示有没有 3,转移是,不在某段末尾的时候,可以转移到下一个异种数字的开头;或者往后拼一个 3,转移到下一个异种数字的开头;或者往后拼一个相同数字。
在某段末尾的时候,\(f_{i,0}\) 如上,但是对于 \(f_{i,1}\),仍然可以拼一个相同数字 \(^{\dagger}\)

但是这里发现状态不够用,要再记录一个,对于有 3 的状态,记录末尾 1/2 的个数,记作 \(x^{\dagger\dagger}\)
\(^{\dagger}\):这里往后拼相同数字,转移到下一段这个数字的,第 \(x+1\) 位。可以认为是,我的 3 可以往后吸纳一些 1/2 进来,然后把原先附在这个 3 后面的字符,往后平移若干段。因为段长不降,所以这个平移的正确性是有保证的。
好的现在已经会 \(O((\sum l_i)^2)\) 了。考虑优化。

首先注意到这个 \(f_{i, 0}\) 的转移是只依赖 \(f_{j, 0}\) 的。容易把同一段的状态压在一起。
\(^{\dagger\dagger}\):我真的要记这一维吗?
注意到 3 只可能出现在一段开头。
又注意到 \(f_{i,1,j}\) 有值,当且仅当 \(j=len\)\(j=len-1\)。其中 \(len\) 是目前 \(i\) 所处的连续段的,前缀长度。
也就是我只需要记,\(i\) 所处的这一段里面,开头是不是 3 就可以知道我这个 \(j\) 是几。
然后你就能写出来这个(几乎是。几乎是。不难改成严格线性的) \(O(\sum l_i)\) 的代码。

void WaterM() {
	cin >> m >> a1, n = 0;
	upw(i, 1, m) cin >> a[i], n += a[i];
	upw(i, 1, m) st[i] = ed[i-1] + 1, ed[i] = ed[i-1] + a[i];
		
	vector<vector<int> > f(n + 2, vector<int>(3));
	f[1][0] = 1;
	f[a[1] + 1][2] = 1;
	upw(i, 1, m) {
		//无3
		//非末尾
		upw(j, st[i], ed[i] - 1) {
			vadd(f[j+1][0], f[j][0]);
			vadd(f[st[i+1]][0], f[j][0]);
			vadd(f[st[i+1]][2], f[j][0]);
		}
		//末尾
		vadd(f[st[i+1]][0], f[ed[i]][0]);
		vadd(f[st[i+2]][2], f[ed[i]][0]);
		
		//有3
		//非末尾
		upw(j, st[i], ed[i] - 1) {
			vadd(f[j+1][1], f[j][1]);
			vadd(f[j+1][2], f[j][2]);
			vadd(f[st[i+1]][1], (f[j][1] + f[j][2]) % P);
			vadd(f[st[i+1]][2], (f[j][1] + f[j][2]) % P);
		}
		//末尾
		vadd(f[st[i+1]][1], (f[ed[i]][1] + f[ed[i]][2]) % P);
		vadd(f[st[i+2]][2], (f[ed[i]][1] + f[ed[i]][2]) % P);
		if(i+2 <= m) {	//特殊转移
			vadd(f[st[i+2] + a[i] - 1][1], f[ed[i]][2]);
			for(int j = i+2; j <= m; j += 2) if(a[j] > a[i]) {
				vadd(f[st[j] + a[i]][1], f[ed[i]][1]);
				break;
			}
		}
	}
	// upw(i, 1, n) {
		// cerr << f[i][1] << ' ' << f[i][2] << '\n';
	// }
	// cerr << '\n';
	int ans = 0;
	upw(i, st[m], ed[m]) vadd(ans, f[i][0]);
	upw(i, 1, m) {
		upw(j, st[i], ed[i]) {
			if((i & 1) == (m & 1) || j == st[i]) vadd(ans, f[j][2]);
			if((i & 1) == (m & 1)) vadd(ans, f[j][1]);
		}
	}
	cout << ans << '\n';
	
	upw(i, 1, m) a[i] = st[i] = ed[i] = 0;
}

现在唯一棘手的就是 \(^{\dagger}\) 处的特殊转移。
那我可以,令新的 \(f'_{i, 1/2}\) 表示原来的 \(f_{st[i],1/2}\)

把转移 DAG 画出来会比较好理解。我们需要处理有关特殊转移,只有两个值:其可能会对后面本段内 \(f_{j,1}\) 产生的影响;以及我如何通过 \(f_{st[i],1}\) 推出 \(f_{ed[i],1}\)
不难。

void WaterM() {
	cin >> m >> a1;
	upw(i, 1, m) cin >> a[i];
	upw(i, 1, m) st[i] = ed[i-1] + 1, ed[i] = ed[i-1] + a[i];
	
	vector<int> g(m+5);
	g[1] = 1;
	upw(i, 2, m) g[i] = a[i-1] * g[i-1] % P;
	
	vector<int> next(m+5);
	dnw(i, m-1, 1) {
		if(a[i+2] > a[i]) next[i] = i+2;
		else next[i] = next[i+2];
	}
	
	vector<vector<int> > f(m+5, vector<int>(3));
	vector<int> extra1(m+5), extra2(m+5);
	f[2][2] = 1;
	upw(i, 1, m) {
		vadd(f[i+1][2], (a[i] - 1) * g[i] % P);
		vadd(f[i+2][2], g[i]);
		
		
		vadd(f[i+1][2], (a[i] - 1) * f[i][2] % P);
		vadd(f[i+1][2], (f[i][1] * (a[i] - 1) + extra1[i]) % P);
		
		vadd(f[i+2][2], f[i][2]);
		vadd(f[i+2][2], (f[i][1] + extra2[i]) % P); 
		
		
		vadd(f[i+1][1], (a[i] - 1) * f[i][2] % P);
		vadd(f[i+1][1], (f[i][1] * (a[i] - 1) + extra1[i]) % P);
		
		vadd(f[i+1][1], f[i][2]);
		vadd(f[i+1][1], (f[i][1] + extra2[i]) % P);
		
		
		//特殊转移
		if(i+2 <= m) {
			// vadd(f[st[i+2] + a[i] - 1][1], f[ed[i]][2]);
			if(a[i] != a[i+2]) {	//流入extra1
				vadd(extra1[i+2], f[i][2] * (a[i+2] - a[i]) % P);
			}
			vadd(extra2[i+2], f[i][2]);
			// cerr << "meow " << i << ' ' << i+2 << ": " << extra2[i+2] << '\n';
			// for(int j = i+2; j <= m; j += 2) if(a[j] > a[i]) {
				// vadd(f[st[j] + a[i]][1], f[ed[i]][1]);
				// break;
			// }
			int j = next[i];
			if(j) {
				// cerr << ' ' << i << ' ' << j << '\n';
				//f[ed[i]][1]的实际值是f[i][1] + extra2[i]
				if(a[i] != a[j] - 1) {	//流入extra1
					vadd(extra1[j], (f[i][1] + extra2[i]) * (a[j] - a[i] - 1) % P);
				}
				vadd(extra2[j], (f[i][1] + extra2[i]) % P);
			}
		}
		// cerr << f[i][1] + extra2[i] << ' ' << f[i][2] << '\n';
	}
	
	int ans = a[m] * g[m] % P;
	// cerr << ans << '\n';
	upw(i, 1, m) {
		int sum = 0;
		if((i & 1) == (m & 1)) {
			vadd(sum, ((a[i] - 1) * f[i][1] + extra1[i] + f[i][1] + extra2[i]) % P);
			vadd(sum, a[i] * f[i][2] % P);
		}
		else {
			vadd(sum, f[i][2]);
		}
		// cerr << i << ": " << sum << '\n';
		vadd(ans, sum);
	}
	cout << ans << '\n';
	
	upw(i, 1, m) a[i] = st[i] = ed[i] = 0;
}

BZOJ4770 图样

不难的一题,为什么我想了一年。
考虑 CF888G,对于一个确定的 \(A\) 数组,MST 是怎么算出来的。
从高往低考虑每一位,把 \(A\) 分成两类,这一位为 \(0\) 的和这一位为 \(1\) 的。这两类分别递归下去做。回溯回来找到这两类之间最小的一条连边就行。

考虑 \(A\) 随机怎么做。发现仍然可以继承上面的想法,每次枚举分类的大小,递归下去做。问题就在于求一个值 \(E(k,i,j)\) 表示,我当前值域是 \([0,2^k)\),两块的大小分别是 \(i,j\),最小连边的期望是多少。

期望转概率,算每个 \(w\in [1,2^k)\),最小连边 \(\ge w\) 的概率。也就要求,这两类中,两两连边权值都要 \(\ge w\)
直接数位 DP 就是对的。为什么我在那里想这个状压那个状压。
转移分类,讨论 \(w\) 某一位是 \(0/1\) 就好。

复杂度 \(O(n^42^m)\)

QOJ1558 大过滤器

神必题目。

首先注意到每一阶段的实际边权可以算出来。就是 \(2^{n-now}\),其中 \(now\) 是当前阶段。Dijkstra 就行。但是要高精度。
然后 ZFR 大神告诉我们 Dijkstra 的时候,记 heap 里面的最小值为 \(x\),那么最大值不会大于等于 \(2x\)。(排除掉所有 dis 为 0 的点)这个归纳是很好证明的。

然后就可以考虑类似 01 BFS 的东西,每次遇到 \(+1\) 边就塞进队首,\(\times 2\) 边就塞进队尾。
正确性的来源是,这个 deque 实际上干了 priority_queue 的事情。利用上面的性质,可以说明这个 deque 内部的元素是不降的。
然后每次取队首,要取出所有 dis 最小的元素,以保证这个这个不降性质。

需要注意的是,不能在入队的时候就确定 dis 并且标记为访问过。想想,因为这个和 01 BFS 还有点差别。

ARC212C ABS Ball

GF 题。C >>>> E。
如果把 GF 写成一个方阵的样子那么大概是这样:

下三角一样,不画。然后一行一行写成封闭形式。变成

\[2\left[\frac x{(1-x)^2}+x^2\frac x{(1-x)^2}+x^4\frac x{(1-x)^2}+\dots\right] \]

也就是

\[\frac 2{1-x^2}\cdot \frac {x}{(1-x)^2} \]

\(m\) 次幂,提取 \(x^n\) 项系数,

\[2^m[x^{n-m}] (1-x)^{-3m}(1+x)^{-m} \]

二项式定理拆开就做完了。

ARC212D Two Rooms

初始设置所有人的 \(ans[i]=\text X\)。然后不断循环,每次找到一个不满足题意的人,就 flip \(ans[i]\)。都满足就输出,结束。

下证正确性。
定义势能函数 \(f=\sum_{i,j\in \text X}a_{i,j}+\sum_{i,j\in \text Y}a_{i,j}\)
我们断言,\(f\) 达到最大值的时候就会有一组解。

考虑反证:若有一个人不满足条件,我就给这个人移到相反房间去。那么因为他不满足条件,所以【同一房间的 \(a\) 之和】小于【不同房间的 \(a\) 之和】,相同变不同,不同变相同,\(f\) 会变大,矛盾。

关于复杂度:我们通过这样的调整,每调整一次至少会使得 \(f\) 增加 \(1\)。而这个 \(f\) 是有上界 \(O(n^2\max a)\) 的。所以总复杂度 \(O(\text{poly } n\max a)\)。取决于调整的复杂度。

CF1163F Indecisive Taxi Fee

删边最短路。场上的改编版,发明出来了,但没有完全发明出来。(指细节挂了)
只说删边最短路的部分。

考虑 \(1~n\) 的一条最短路,称为主链。那删边如果删的不是主链上的就输出原最短路是不是就好。考虑删一条主链上的边,最短路会变成什么样子。
大概就是,先走了主链上的一段前缀,在外面走了一会,再走主链的一段后缀,到达终点。

然后这里以 \(1\) 为根建出来一个最短路树 \(tr1\) 。其中这棵树要求,某一主链外的点,尽可能早地从主链中分出来。(直观理解是,这样可以贡献到更多的,主链上的删边)
\(n\) 为根同理建出来一个 \(tr2\)

注意到一件事情:考虑主链外面的点对删边的贡献,一条可能成为某个询问的答案的路径,最多经过一条既不在 \(tr1\),也不在 \(tr2\) 的边。

考虑反证:如果有两条,考虑这两条边的公共点 \(u\)。大概就是,这个走两条边的路径,一定会被,从树边直接到 \(u\) 的两条路径中的一条毙掉,因为边权非负。

那就做完了,不经过非树边的路径贡献是好求的,经过一条的,\(O(m)\) 枚举就行,所以总共的贡献点对是 \(O(n+m)\) 的。
对于询问,做一个二维数点。复杂度 \(O((n+m+q)\log n)\)

模拟赛题

题意:给定一棵 \(2^{n+1}-1\) 个点的满二叉树。层序标号。现在我把相邻的叶子(\(2^n+x\leftrightarrow 2^n+x+1,x\in[0, 2^n-1)\))连无向边。求一种染色方案,使得:

  • 设一个新图 \(G\) 中的两个点 \(u,v\) 有边,当且仅当在原来的“树”中,有 \(x\ne y, c_x=u,c_y=v\)\(x,y\) 之间有边。要使得 \(G\) 是一个树。

每种颜色染的次数不能超过 \(12\)。$n\le 20

image
递归下去做。这种方法可以使每种颜色染的次数是 \(O(n)\) 的。
image
回文地染,就能达到 \(12\)

ARC212F Add Integer

注意到,如果我确定了 \(A_n\)\(A_{n-1}\),那么我们可以唯一地确定 \(A_1\)\(A_2\)。递推式是 \(A_i=|A_{i+1}-A_{i+2}|\)。至于值域的限制,只需要考虑枚举的 \(A_{n-1}\in [0,m]\) 就行。
那么现在就有了一个 \(O(nm)\) 的做法了。

考虑加速。
瓶颈在于递推 \(A_i\)。发现我们可以加速这个过程。
\(A_n=x,A_{n-1}=y\),那么递推的过程可以写成:

\[[x,y,x-y],[x-2y,y,x-3y],[x,4y,y,x-5y],\dots \]

框出来了类周期为 \(3\),直到某个 \(k\)\(x-ky < y\)
然后对于这样的递推,可以直接算出这个 \(k\)\(O(1)\) 地完成这一串。

又不难发现,经过 \(O(1)\) 组这样的过程,\(x\) 一定会减半。所以这样个过程只会进行 \(O(\log m)\) 次。总复杂度 \(O(m\log m)\)。口胡一时爽,细节______。
upd: 是不是细节也还好。用时间换细节,把一些容易出问题的地方放松一点,最后循环个 20 次跑一下就好。

P7962

世纪名题。交换差分。
然后注意到最优解的差分序列一定一段递减,一段递增。
给差分序列排序。然后设状态 \(f_{i,j}\) 表示考虑前 \(i\) 个,此时 \(\sum a_k=j\),然后转移分类,考虑新的 \(d_i\) 插到序列开头还是末尾。

转移是简单的。然后复杂度是 \(O(na\min\{n,a\})\) 的,因为 \(d_i=0\) 是无用的,过掉就好。

P5046

这一一道计数题。
直接考虑判定某个权值的生成树有没有。然后这里转化成数方案数。模数的话爱咋样咋样,只有 \(\frac 1P\) 的概率 4 掉。

我改一下,对于一条边的边权,设为一个多项式(集合幂级数) \(x^{v_i}\)。那矩阵树干的事情就是求边权的乘积的和。这里我定义这个乘法为,位运算卷积。即,按照题目的叙述,每一位做不同的位运算卷积。
这里给每条边的多项式 FWT 一下,然后放在矩阵树上。然后行列式完了之后结果是一个多项式,再给 IFWT 回来就好。

QOJ783

article

ARC198E

花费 1s 时间翻译题意得到一个 DP。设 \(f_i\) 表示我得到了 \(i\) 的方案数,转移自然是枚举一个 \(j\)\(f_i \to f_{(i \operatorname{ or }s_j) + 1}\)。然后就花费一年时间想这个转移有什么性质,如此如此。

上面的式子不好看,因为他还和 \(j\) 有关。尝试写成一个更像 or 卷积的形式。记 \(a_i=\#[s_j=i]\),然后就可以 \(f_{S+1}\gets \sum_{i \cup j=S}f_i\times a_j\)。然后我获知了这一点之后就在那里想我应该怎么把 FWT 套进分治的结构。

但 FWT 和 FFT 不同的一点是,FFT 的下标是可减的,但是 FWT 不行。
也就是分治的过程中可能要求我需要对一整段前缀做 FWT 的情形,复杂度不可接受。目前不知道怎么避免,有大手子能 q 我一下。

那考虑上面这个式子能怎么写。这可能是套路。记 \(g_S=f_{S+1}\),然后可以写成

\[\begin{aligned} g_S&=\sum_{i \cup j=S}f_i\times a_j\\ &=(\sum_{i\subseteq S}a_i)(\sum_{i\subseteq S}f_i)-\sum_{i\subsetneq S}g_i \end{aligned} \]

然后要求一个半在线前缀和。

考虑高维前缀和平常都是,先枚举维数再枚举 mask,但是由分治结构的要求,相当于先枚举 mask,会算重。
这里引入撤销操作,相当于强制我分治到哪一层,我的前缀和就只能统计到这么多维的贡献。

CF878E

好像是一道著名题目。

首先有一个区间 DP。然后显然没有任何前途。我们需要一个足够确定的策略。
尝试刻画贡献:每个 \(a_i\) 的系数都形如 \(2^{k_i}\)
然后这里有一个结论:一个贡献序列 \(k\) 合法,当且仅当 \(k_1=0\),且对于 \(i>1,1 \le k_i\le k_{i-1}+1\)。必要性显然,充分性也不难归纳。
(这里这种东西怎么看出来?不懂。)

然后考虑一个贪心:最优解的结构,对于所有 \(i>1\),要么 \(k_i=1\),要么 \(k_i=k_{i-1}+1\)
这个也是显然的,考虑反证,那么对于和大于 \(0\) 的极长公差为 \(1\) 的子段一定可以整体加一;和小于 \(0\) 的整体减一。

那现在我们就把最优解的结构刻画成了:形如若干个极长的 \(1\) 为首项,\(1\) 为公差的等差数列(第一段特殊处理一下)。且每一段 \(a\) 的和均小于等于 \(0\)
考虑维护前缀的答案是什么样一个状态。
在末尾添加一个数,如果是正的就 append 到上一段末尾后面去,然后这样可能会引起“上一段的和也变成正的”,暴力做这个连锁反应就好。负的就新开一段。

考虑询问,这里需要把询问挂在右端点上。原因是,如果不离线,后面新增的一个很大的正数可能会吞并掉 \(l,r\),从而使得策略对于 \([l, r]\) 不优。

假设维护出来一个 \(sum_i\) 表示前缀的答案,那么整段之间策略显然独立。你 \(l\) 可能切出来一个整段的后缀。其他部分前缀和减一减就好。
那么对于 \(l\) 的这一段后缀,维护所有整段的后缀答案。这里发现,我们虽然在 append 的过程中会合并段,但是我们如果单取一段整段的后缀,最优解仍然不会分裂。那弄个后缀和除掉 \(2\) 的多少次幂就好了。

但是这里注意,我们要对第一段特殊处理(\(\times \frac 12\)),因为 \(k_1=0\)
复杂度瓶颈在于离线下来之后查对应左端点对应的段编号。一个老哥下班。

AGC023F

之前模考见过一遍这个套路。见到本尊再被肘一次。被称为 01 on tree trick。

正着不是很能考虑清楚,考虑反着来。即,我们每次考虑儿子合并到父亲上面,计算这个贡献,并尝试得出一个合并策略。
这种问题似乎被称为【树上的全序问题】?这种可以直接比较出来某两个结点哪个放在前面更优的,好像都可以直接按这种方法搞。

那合并的过程弄个堆或者 set 维护一下,并查集维护祖先关系就好。
然后排序规则也是显然的,考虑邻项交换不难得出 \(u\)\(v\) 前面,要求 \(\frac{cnt1_u}{cnt0_v} < \frac{cnt1_v}{cnt0_v}\)。复杂度 \(O(n\log n)\)

HDU6326

打怪兽模型。原来模考原题在这里。

这也是一个树上全序问题。考虑顺序是什么。
首先对于 \(a_i\le b_i\) 的情况,肯定比所有 \(a_i>b_i\) 的都先选。然后内部的顺序就是按照 \(a_i\) 的升序去选。直观理解就是,门槛递增。
然后考虑 \(a_i>b_i\) 的情形。那么 \(i\)\(j\) 优,等价于 \(\max(a_i, a_j+a_i-b_i)<\max(a_j,a_i+a_j-b_j)\)。然后这个因为 \(a>b\) 所以得出 \(b_i>b_j\)

引用上一题 AGC023F 的手法。儿子向父亲合并。
然后合并的时候注意一点就是,我们希望合并之后 \(a'\)\(b'\) 的【效果】等同于先打父亲在打儿子。要一点小计算。

UOJ418

经典题。仍然接着上面,是树上全序问题。
不是哥们,你倒着考虑问题,那和上面打怪兽不是一模一样。\(a_u=w_u,b_u=\sum_{v}a_v\)
额要稍作修改一下,答案统计的时候拿二元组 \((w,0)\) 加上合并出来的东西。

那现在她让我们求子树的答案。
额你可以先算根的答案,弄出来全局的一个合并优先级。
那么子树的合并顺序,是全局合并顺序的一个子序列

按照合并顺序,每考虑到一个点——
考虑类似 01 on tree 的合并的过程到底在干嘛。那么可以看成一个点,在某个祖先的一条链上,都贡献了一次。那么这个剖子 + 线段树维护,做到两个老哥,可以通过。

不懂她们线段树合并都在干嘛/yun 为什么还有平衡树的???

P6893

显然离散和连续是两个独立部分。
对于连续的,注意到价值的形式可以看做一次函数 \(f(x)=-\Delta t\cdot x+t_0\) 的积分,也就是当前某个物品的“性价比”就是这个一次函数。
然后我们知道连续重量的背包贪就好。那显然每次取这个函数值最大的,直到出现 \(f_1(x)=f_2(x')\)。那这个时候不管是 \(x\) 还是 \(x'\) 多选了 eps 的重量,另一个肯定也跟着选。

于是可以看成是两个物品合并。然后合并之后的等价效果仍然可以用二次函数描述。
具体地,两个物品总共选 \(x\) 个时,第一个物品 \(x\frac{\Delta t_2}{\Delta t_1+\Delta t_2}\),第二个物品跟着选 \(x\frac{\Delta t_1}{\Delta t_1+\Delta t_2}\) 的重量。那么等效的二次函数就是

\[t_0x\frac{\Delta t_1}{\Delta t_1+\Delta t_2}-\frac 12\Delta t_1(x\frac{\Delta t_1}{\Delta t_1+\Delta t_2})^2+t_0x\frac{\Delta t_2}{\Delta t_1+\Delta t_2}-\frac 12 \Delta t_2(x\frac{\Delta t_2}{\Delta t_1+\Delta t_2})^2 \]

等于

\[t_0x-\frac 12 \frac{\Delta t_1\Delta t_2}{\Delta t_1+\Delta t_2}x^2 \]

然后实现上可以假设每次合并完之后,重量又从 0 开始。

然后对于离散的部分。说是凸优化所以花费 1s 考虑一个朴素 \(O(nW^2)\) 的背包,即枚举第 \(i\) 个物品选多少个。然后这里由于二次函数是上凸的所以可以直接决单。分治地做就好,\(O(nW\log W)\)
做的时候按照 \(\bmod w_i\) 的剩余系分个类就可以。

这里提一嘴,也可以直接按照【珠宝】一题的手法,\(w_i\) 相同的分到一起,取前 \(\lfloor W/w_i\rfloor\) 个。
于是物品个数只有 \(O(W\log W)\) 个。总复杂度 \(O(W^2\log W)\) 居然能过。

一道模考题

题意:给定简单无向图 \(G\),设 \(G\) copy \(k\) 遍后的补图为 \(H\),求 \(H\) 的生成树个数。\(|V_G|\le 100,k\le 10^8\)。对给定模数(不一定为质数)取模。

因为 copy 了 \(k\) 遍之后边集很大。考虑容斥。
那我们给包含了 \(G\) (注意不是 \(H\))中某个边集 \(S\) 的生成树一个权值,使得只有包含了空集的生成树最后总权值为 \(1\)。容易发现设置为 \((-1)^{|S|}\) 即可。

那这个 \(S\) 会给点集连接成 \(nk-|S|\) 个连通块。考虑 Cayley 定理,枚举连通块的大小连接成的序列,答案即为:

\[\sum_{a}(-1)^{|a|}(\prod a_i)(nk)^{|a|-2} \]

(额我枚举【连通块大小序列】和枚举 \(S\) 并不是完全等价的。但是这里我可以理解 \(a_i\) 为第 \(i\) 个连通块的大小,乘上内部钦定成树的方案数)即

\[(nk)^{-2}\sum_{a}(\prod a_i)(-nk)^{|a|} \]

然后由于 \(G\) 的各个副本中间是没有连边的,所以 \(k\) 个部分几乎是独立的。可以只算单个,然后 \(k\) 次方。这就是容斥的精髓:干掉 \(k\),化简问题。答案变成

\[(nk)^{-2k}(\sum_{a}(\prod a_i)(-nk)^{|a|})^k \]

考虑里面的和积式怎么算。这里是 Matrix-Tree 定理的精髓——合理设置权值。设置一个虚点,向 \(G\) 的每一个点连边,权值为 \(-nk\)
然后这里和积式就是矩阵树算出来的行列式。
(正确性是显然的,因为考虑每一种钦定连通块的方式,相当于钦定了一个森林;然后我加了一个虚点,相当于就是一棵树)

最后就是这个 \((nk)^{-2k}\) 有点搞不了,考虑在算行列式的时候,我直接给矩阵最后一行所有元素手动除以 \(nk\),那 det 的值也会除以 \((nk)^{nk}\)。这样就可以避免求逆元,合数模数也能做!

posted @ 2025-12-28 22:44  Water_M  阅读(14)  评论(0)    收藏  举报