Bootstrap

《算法竞赛进阶指南》做题笔记

1. P10447 最短 Hamilton 路径

著名 NPC 问题。

发现 n ≤ 20 n \le 20 n20,考虑状压 dp。那么设 f i , j f_{i,j} fi,j 表示当前在第 i i i 个点, j j j 是一个 n n n 位二进制数, j j j 在二进制下第 i i i 位表示第 i i i 个点是否已经到达。那么对于这个状态,我们直接找下一个该去哪个点。也就是,假设枚举到下一个去第 k k k 个点,那么有转移方程

f k , j + 2 n − k = min ⁡ ( f k , j + 2 n − k , f i , j + d i s i , k ) f_{k,j+2^{n-k}}=\min(f_{k,j+2^{n-k}},f_{i,j}+dis_{i,k}) fk,j+2nk=min(fk,j+2nk,fi,j+disi,k)

那么最终答案就是 f n , 2 n − 1 f_{n,2^n-1} fn,2n1。时间复杂度 O ( n 2 2 n ) O(n^22^n) O(n22n),可以通过。

for(int j = 0;j < (1 << n);j++) {
  for(int i = 1;i <= n;i++) {
    if(!(j & (1 << (n-i)))) continue;
    for(int k = 1;k <= n;k++) {
      if((j & (1 << (n-k)))) continue;
      f[k][j + (1 << (n-k))] = min(f[k][j + (1 << (n-k))], f[i][j] + dis[i][k]);
    }
  }
}

2. P2114 [NOI2014] 起床困难综合症

考虑按位计算答案。我们从高到低枚举第几位,对于每一位,计算如果初始时这一位是 0 / 1 0/1 0/1 的最终结果。

然后讨论:

  • 如果可以让 0 → 1 0 \to 1 01,直接选择 0 0 0
  • 否则,如果可以让 1 → 1 1 \to 1 11,那么判断一下是否能用 1 1 1,可以就用。

时间复杂度 O ( n log ⁡ m ) O(n \log m) O(nlogm)

for(int i = 30;i >= 0;i--) {
  int a = calc(i, 0), b = calc(i, 1);
  if(a) ans += (1 << i);
  else if(b && (m >= (1 << i))) m -= (1 << i), ans += (1 << i); 
} cout << ans << endl;

3. P10449 费解的开关

还没做,,

4. P1593 因子和

先扔个结论:

如果已知一个正整数 n = p 1 a 1 p 2 a 2 … p k a k n=p_1^{a_1}p_2^{a_2}\dots p_k^{a_k} n=p1a1p2a2pkak,其中 p i p_i pi 为互不相同的质数, a i a_i ai 为正整数,则 n n n 的所有因子之和为 ∏ i = 1 k ∑ j = 0 a i p i j \displaystyle\prod_{i=1}^{k}\sum_{j=0}^{a_i}p_i^j i=1kj=0aipij


回到本题。假设 a = p 1 a 1 × p 2 a 2 × … p k a k a=p_1^{a_1}\times p_2^{a_2}\times\dots p_k^{a_k} a=p1a1×p2a2×pkak,那么显然 a b = p 1 a 1 × b × p 2 a 2 × b × … p k a k × b a^b=p_1^{a_1\times b}\times p_2^{a_2\times b}\times\dots p_k^{a_k\times b} ab=p1a1×b×p2a2×b×pkak×b。再套用上面结论,有

a n s = ( 1 + p 1 + p 1 2 + ⋯ + p 1 a 1 ) × ( 1 + p 2 + p 2 2 + ⋯ + p 2 a 2 ) × ( 1 + p k + p k 2 + ⋯ + p k a k ) ans=(1+p_1+p_1^2+\dots+p_1^{a_1})\times(1+p_2+p_2^2+\dots+p_2^{a_2})\times(1+p_k+p_k^2+\dots+p_k^{a_k}) ans=(1+p1+p12++p1a1)×(1+p2+p22++p2a2)×(1+pk+pk2++pkak)

然后会发现是等比数列。那么和怎么求呢?这里推导一下:

x = 1 + p 1 + p 1 2 + ⋯ + p 1 a 1 x=1+p_1+p_1^2+\dots+p_1^{a_1} x=1+p1+p12++p1a1。那么将左右两边同时 × p 1 \times p_1 ×p1,得到 x p 1 = p 1 + p 1 2 + p 1 3 + ⋯ + p 1 a 1 + 1 xp_1=p_1+p_1^2+p_1^3+\dots+p_1^{a_1+1} xp1=p1+p12+p13++p1a1+1。用后面这个式子减去前面的,得到

( p 1 − 1 ) x = p 1 a 1 + 1 − 1 (p_1-1)x=p_1^{a_1+1}-1 (p11)x=p1a1+11

所以 x = 1 + p 1 + p 1 2 + ⋯ + p 1 a 1 = p 1 a 1 + 1 − 1 p 1 − 1 x=1+p_1+p_1^2+\dots+p_1^{a_1}=\dfrac{p_1^{a_1+1}-1}{p_1-1} x=1+p1+p12++p1a1=p11p1a1+11

这样整个式子就变成了:

a n s = p 1 a 1 + 1 − 1 p 1 − 1 × p 2 a 2 + 1 − 1 p 2 − 1 × ⋯ × p k a k + 1 − 1 p k − 1 ans=\dfrac{p_1^{a_1+1}-1}{p_1-1}\times\dfrac{p_2^{a_2+1}-1}{p_2-1}\times\dots\times\dfrac{p_k^{a_k+1}-1}{p_k-1} ans=p11p1a1+11×p21p2a2+11××pk1pkak+11

这样就可以可以用逆元和快速幂解决。然后,你会发现你获得了 82 ∼ 88 82 \sim 88 8288 分()

问题出在逆元部分。逆元的要求是操作数和模数必须互质,但是有可能 ( p − 1 )   m o d   9901 = 0 (p-1) \bmod 9901=0 (p1)mod9901=0,也就是 p   m o d   9901 = 1 p \bmod 9901 = 1 pmod9901=1,此时是没有逆元的。那么此时我们会发现, 1 + p i + p i 2 + ⋯ + p i a i ≡ 1 + 1 + 1 + ⋯ + 1 ( m o d 9901 ) 1+p_i+p_i^2+\dots+p_i^{a_i}\equiv1+1+1+\dots+1\pmod {9901} 1+pi+pi2++piai1+1+1++1(mod9901),也就是说这个式子和 a i + 1 a_i+1 ai+1 同余。那么统计答案的时候判断一下是否有逆元,如果没有那么就乘上 ( a i + 1 ) (a_i+1) (ai+1) 即可。

这样即可通过。注意,此时有可能你 94 94 94 分,此时记得开 long long

signed main() {
	int a, b; cin >> a >> b; 
	if(isp(a)) mp[a] += b; else {
		for(int i = 2;i * i <= a;i++) { 
			if(isp(i) && a % i == 0) {
				while(a % i == 0) {
					a /= i; mp[i] += b;
				}
			} if(isp(a)) { mp[a] += b; break; } 
		} 
	} int ans = 1; for(auto i : mp) {
		if(i.first % mod == 1) { ans = (ans * (i.second+1)) % mod; continue; }
		ans = (ans * (power(i.first, i.second+1) - 1 + mod) % mod * power(i.first - 1, mod - 2) % mod);
	} cout << ans;
	return 0;
}

3. P1842 [USACO05NOV] 奶牛玩杂技

一眼贪心。但是,怎么贪呢?

多试几次可以发现是按照 w + s w+s w+s 从小到大排序,排好序之后从上到下摆放。但是这样为什么对?

假设 w 1 , s 1 w_1,s_1 w1,s1 w 2 , s 2 w_2,s_2 w2,s2 是两头奶牛。这里有 w 1 + s 1 ≤ w 2 + s 2 w_1+s_1\le w_2+s_2 w1+s1w2+s2。此处假设没有其他奶牛。

如果 w 1 , s 1 w_1,s_1 w1,s1 在上面,那么第二头的压扁指数为 w 1 − s 2 w_1-s_2 w1s2。如果 w 2 , s 2 w_2,s_2 w2,s2 在上面,那么第一头的压扁指数为 w 2 − s 1 w_2-s_1 w2s1。那么因为 w 1 + s 1 ≤ w 2 + s 2 w_1+s_1\le w_2+s_2 w1+s1w2+s2,所以(移项就可以得到) w 1 − s 2 ≤ w 2 − s 1 w_1-s_2\le w_2-s_1 w1s2w2s1,所以第一头奶牛放在上面更优。

至此我们证明了这种贪心是对的。那么排序计算即可,时间复杂度 O ( N log ⁡ N ) O(N \log N) O(NlogN)

#include <bits/stdc++.h>
using namespace std;
struct cow {
	int w, s; bool operator < (const cow& y) const {
		return w + s < y.w + y.s;
	}
} a[50005];
int main() {
	int n; cin >> n;
	for(int i = 1;i <= n;i++) cin >> a[i].w >> a[i].s; sort(a+1, a+n+1);
	int sum = 0, ans = -0x7fffffff; for(int i = 1;i <= n;i++) {
		ans = max(ans, sum - a[i].s); sum += a[i].w;
	} cout << ans << endl;
	return 0;
}
;