《算法竞赛进阶指南》读书笔记汇总
这里面是我在阅读《算法竞赛进阶指南》这本书时的一些思考,有兴趣可以瞧瞧!
如若发现什么问题,可以通过评论或者私信作者提出。希望各位大佬不吝赐教!
许多问题都可以使用二叉堆来进行优化,下面直接看例题吧。
基本思想
倍增优化dp主要包括这样两个步骤:预处理和二进制拼凑答案。
【例题】开车旅行(AcWing293)
题目链接
思路:
这道题非常麻烦非常麻烦非常麻烦!
首先是预处理部分,我们预处理出以下数组:
预处理方式:
预处理出五个数组之后,我们就可以利用这些已知信息进行二进制拼凑,也就是倍增求解问题。
我们先看问题二,我们从大到小枚举旅行的距离(二进制枚举),判断当前A、B已经走的距离加上枚举的距离是否小于等于给定的
X
X
X,即将
X
X
X做二进制拆分。拆分过程中利用已经预处理好的数组计算AB分别走过的距离即可。
对于问题一,我们可以枚举所有的起点,更新最小值即可。
AC代码:
#include <algorithm>
#include <iostream>
#include <cstring>
#include <set>
#include <vector>
using namespace std;
typedef long long LL;
typedef pair<LL, int> PLI;
const int N = 100010, M = 17;
const LL INF = 1e12;
int n;
int h[N];
int ga[N], gb[N];
int f[M][N][2];
LL da[M][N][2], db[M][N][2];
void init_g()
{
set<PLI> S;
S.insert({INF, 0}), S.insert({INF + 1, 0});
S.insert({-INF, 0}), S.insert({-INF - 1, 0});
for (int i = n; i; i -- )
{
PLI t(h[i], i);
auto j = S.lower_bound(t);
j ++;
vector<PLI> v;
for (int k = 0; k < 4; k ++ )
{
v.push_back(*j);
j --;
}
LL d1 = INF, d2 = INF;
int p1 = 0, p2 = 0;
for (int k = 3; k >= 0; k --)
{
LL d = abs(h[i] - v[k].first);
if (d < d1)
{
d2 = d1, d1 = d;
p2 = p1, p1 = v[k].second;
}
else if (d < d2)
{
d2 = d;
p2 = v[k].second;
}
}
ga[i] = p2, gb[i] = p1;
S.insert(t);
}
}
void init_f()
{
for (int i = 0; i < M; i ++ )
for (int j = 1; j <= n; j ++ )
{
if (!i) f[0][j][0] = ga[j], f[0][j][1] = gb[j];
else
{
for (int k = 0; k < 2; k ++ )
{
if (i == 1) f[1][j][k] = f[0][f[0][j][k]][1 - k];
else f[i][j][k] = f[i - 1][f[i - 1][j][k]][k];
}
}
}
}
int dist(int a, int b)
{
return abs(h[a] - h[b]);
}
void init_d()
{
for (int i = 0; i < M; i ++ )
for (int j = 1; j <= n; j ++ )
{
if (!i)
{
da[0][j][0] = dist(j, ga[j]), da[0][j][1] = 0;
db[0][j][1] = dist(j, gb[j]), db[0][j][0] = 0;
}
else
{
for (int k = 0; k < 2; k ++ )
{
if (i == 1)
{
da[1][j][k] = da[0][j][k] + da[0][f[0][j][k]][1 - k];
db[1][j][k] = db[0][j][k] + db[0][f[0][j][k]][1 - k];
}
else
{
da[i][j][k] = da[i - 1][j][k] + da[i - 1][f[i - 1][j][k]][k];
db[i][j][k] = db[i - 1][j][k] + db[i - 1][f[i - 1][j][k]][k];
}
}
}
}
}
void calc(int x, int s, int &la, int &lb)
{
la = lb = 0;
for (int i = M - 1; i >= 0; i -- )
{
if (f[i][s][0] && la + lb + da[i][s][0] + db[i][s][0] <= x)
{
la += da[i][s][0], lb += db[i][s][0];
s = f[i][s][0];
}
}
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) scanf("%d", &h[i]);
init_g();
init_f();
init_d();
int s, x;
scanf("%d", &x);
int ans = 0, max_h = 0;
double min_ratio = INF;
for (int i = 1; i <= n; i ++ )
{
int la, lb;
calc(x, i, la, lb);
double ratio = lb ? 1.0 * la / lb : INF;
if (ratio < min_ratio || ratio == min_ratio && max_h < h[i])
{
min_ratio = ratio;
max_h = h[i];
ans = i;
}
}
printf("%d\n", ans);
int m;scanf("%d", &m);
while(m -- )
{
scanf("%d%d", &s, &x);
int la, lb;
calc(x, s, la, lb);
printf("%d %d\n", la, lb);
}
return 0;
}
【例题】计算重复(AcWing294)
题目链接
思路:我们考虑这样一个问题,计算出
c
o
n
n
(
s
1
,
n
1
)
conn(s1, n1)
conn(s1,n1)生成的
c
o
n
n
(
s
2
,
n
2
)
conn(s2, n2)
conn(s2,n2)的数量
m
′
m'
m′,那么原问题的解就是
m
=
m
′
/
n
2
m = m' / n2
m=m′/n2。
同样是一个倍增的问题,可以通过预处理和二进制拼凑来求解。
首先预处理这样一个数组:
然后倍增过程也是比较简单滴
AC代码:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 110, M = 31;
int n1, n2;
string s1, s2;
LL f[N][M];
void solve()
{
int sz = s1.size();
bool flag = false;
for (int i = 0; i < sz; i ++ )
{
f[i][0] = 0;
int p = i;
for (int j = 0; j < s2.size(); j ++ )
{
int cnt = 0;
while(s1[p] != s2[j])
{
p = (p + 1) % sz;
cnt ++;
if (cnt >= sz)
{
flag = true;
break;
}
}
p = (p + 1) % sz;
f[i][0] += cnt + 1;
if (flag) break;
}
if (flag) break;
}
if (flag)
{
cout << 0 << endl;
return ;
}
for (int j = 1; j < M; j ++ )
for (int i = 0; i < sz; i ++ )
f[i][j] = f[i][j - 1] + f[(i + f[i][j - 1]) % sz][j - 1];
LL ans = 0;
for (int i = 0; i < sz; i ++ )
{
LL p = i, t = 0;
for (int j = M - 1; j >= 0; j -- )
{
if (p + f[p % sz][j] <= sz * n1)
{
p += f[p % sz][j];
t += 1 << j;
}
}
ans = max(ans, t);
}
cout << ans / n2 << endl;
}
int main()
{
while(cin >> s2 >> n2 >> s1 >> n1)
{
solve();
}
return 0;
}