2.3 acwing.1078. 旅游规划(蓝桥压轴级别)
《信息学奥赛一本通》
1 | W 市的交通规划出现了重大问题,市政府下定决心在全市各大交通路口安排疏导员来疏导密集的车流。 |
思路:
考察树形DP。
题目中的最长路径也就是指树的直径,在蓝桥杯总结(二一)提到。
在AcWing 1207. 大臣的旅费中用了2次dfs来求树的直径。
本题介绍树形DP解法求树的直径。(题目要求最长路径上所有点)
观察树的形状我们可以直到每条直径都必然有一个最高点,可以将以哪个点作为最高点作为集合划分的依据。
对于每个最高点i,我们只要求出每个子结点往下走的最大长度d1(i)和次大长度d2(i)(从树的形状可以直观地理解),d1(i)、d2(i)加起来就是经过最高点i的直径。
求出以每个结点为最高点的直径,取max就得到树的直径ans了。
直径求法的正确性证明如下:
参考题解:https://www.acwing.com/solution/content/8986/
那么问题来了,如何判断一个结点i是否在一条直径上呢?
从结点i出发走到树的尽头一共有三条可能的最长路径:1.往下走,最大长度d1(i)和次大长度d2(i),上面已经求出;2.通过父结点j往上走,最大值用数组up[j]记录;3.通过父结点j往(下)子结点走,又分为2种情况,当d1(j)
经过结点i时,结点i通过父结点j走到尽头的最长路径就是up[i] = max(up[j]+1,d2(j)+1)
,当d1(j)
不经过结点i时,那么这个最长路径就是up[i] = max(up[j]+1,d1(j)+1)
。(对照下图)
最后,如果对d1(i),d2(i)以及up[i]中的三个取最大的两个,加起来记为人,如果r = ans
,那么经过结点i的最大路径=树的直径,说明结点i在一条直径上。
求树的直径的过程是自下而上的,因为父结点往下走的长度取决于子结点往下走的长度;判断一个结点是否在一条直径上的过程是自上而下的,因为求子结点往上走的长度取决于父结点往上走的长度。
两次dfs都是O(n)的,所以时间复杂度是O(n)。
注意:交叉路口下标从0开始。
还是用数组模拟邻接表。
1 |
|
2.4 acwing.1303. 斐波那契前 n 项和
《信息学奥赛一本通》
1 | 大家都知道 Fibonacci 数列吧,f1=1,f2=1,f3=2,f4=3,…,fn=fn−1+fn−2。 |
思路:
n的范围是210^9,m的范围是10^9+10。数据范围特别大,考察矩阵快速幂,也就是*矩阵运算+快速幂。
参考文章:y总求解Fibonacci数列的若干方法
文中介绍了几种算法。
其中递归时间复杂度为O(2^n),最慢,最常见的做法。
在众多算法中,矩阵快速幂时间复杂度是O(logn),最快。
补充一种通项公式解法:
时间复杂度可能和矩阵快速幂差不多(个人猜测),但是数据量特大时精度会降低很多!(不太推荐使用)
1 | int Fibonacci(int n) { |
算法:矩阵快速幂。
首先要掌握快速幂算法,求 m^k%p,时间复杂度 O(logk)。
参考文章:快速幂算法
问题:如何构造Fibonacci数列的矩阵?
定义序列$f_i$为Fibonacci数列的第i项,i从1开始。
我们首先定义一个向量:$F_n = [f_n\ f_{n+1}],F_{n+1} = [f_{n+1}\ f_{n+2}]$,然后可以找到一个矩阵A使得:$F_{n+1} = F_n A$。
不难得出:$A = \left[ \begin{matrix} 0 & 1 \\ 1 & 1 \end{matrix} \right]\ (f_{n+2} = f_n +f_{n+1})$
由此可以得到递推公式:$F_n = F_1 A^{n-1}$。
利用快速幂的算法我们能够很快计算出$A^n$,只是把数字换成了矩阵。
回到题目,我们要求的是$f_n$的前n项和$S_n\ mod\ m$,所以我们构造的向量需要多加一个维度$S_n$。
$F_n = [f_n\ f_{n+1}\ S_n],F_{n+1} = [f_{n+1}\ f_{n+2}\ S_{n+1}]$,然后可以找到一个矩阵A使得:$F_{n+1} = F_n A$。
不难得出:$A = \left[ \begin{matrix} 0 & 1 & 0 \\ 1 & 1 & 1 \\ 0 & 0 & 1 \end{matrix} \right]$
由此可以得到递推公式:$F_n = F_1 A^{n-1}$。
代码:
参考1:y总快速幂模板
参考2:快速幂算法
参考3:https://www.acwing.com/solution/content/8881/
1 | // 快速幂模板,二进制迭代写法 |
因为快速幂的时间复杂度是$O(logn)$,而第二个mul函数的for循环次数为$3^3$,所以本题的时间复杂度为$O(3^3*logn)$。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49// AC代码
using namespace std;
typedef long long LL;
const int N = 3;
int n,m;
void mul(int c[],int a[],int b[][N]){// 计算F1 = F1 * a
int temp[N] = {0};
for (int i = 0;i < N;i++)
for (int j = 0;j < N;j++)
temp[i] = (temp[i] + (LL)a[j]*b[j][i]) % m;
memcpy(c,temp,sizeof temp);// 注意:sizeof后面不能写c,是指针的大小
}
void mul(int c[][N],int a[][N],int b[][N]){// 计算a = a * a
int temp[N][N] = {0};
for (int i = 0;i < N;i++)
for (int j = 0;j < N;j++)
for (int k = 0;k < N;k++)
temp[i][j] = (temp[i][j] + (LL)a[i][k]*b[k][j]) % m;
memcpy(c,temp,sizeof temp);
}
int main(){
scanf("%d%d",&n,&m);
// 计算初始向量F_1
int F1[3] = {1,1,1};
int a[N][N] = {// 快速幂矩阵a
{0,1,0},
{1,1,1},
{0,0,1}
};
n--;// 矩阵快速幂求F_n
while (n){// n&1,偶数返回1,否则返回0
if (n & 1) mul(F1,F1,a);// F1 = F1 * a
mul(a,a,a);// a = a * a
n >>= 1;
}
printf("%d\n",F1[2]);
return 0;
}