算法竞赛进阶指南 0x57 倍增优化DP

前方高能!!!

总论

可以使用倍增的情况是这一种情况可以任意划分。

image

AcWing\293. 开车旅行

image
image
image

输入样例:

10
4 5 6 1 2 3 7 8 9 10
7
10
1 7
2 7
3 7
4 7
5 7
6 7
7 7
8 7
9 7
10 7

输出样例:

2
3 2
2 4
2 1
2 4
5 1
5 1
2 1
2 0
0 0
0 0

这一道题目真的是难中难!!!

注意题目中所说的“且编号较小的城市在编号较大的城市的西边,已知各个城市的海拔高度互不相同,记城市 i 的海拔高度为 Hi”.注意这些城市并不是排列在同一条线上,可能是1号到3号的距离更近。但是总体上的趋势是往东走。

现在思考,对于每一个点,都会向东走,走到距离最小或者是最小的地方。

由于:

  1. 各个城市的海拔高度互不相同
  2. 当前城市到两个城市的距离相同,则认为离海拔低的那个城市更近

所以每一个点(无论是A开车还是B开车)到达的下一个点的位置是固定的。SO?如果确定了起始点,那么所走的路线是唯一固定的。

part1 确定ga[], gb[]

由于最大值与最小值是在目前所处的点的东方,所以遍历的时候从右向左。遍历完一个就加上值。

使用set来求邻值。

对set的使用方法进行分析:

image

邻值肯定在与当前值最近的地方。如果最近的是2,那么次近的就是1或者3. 反之,如果最近的是3,那么次近的是2或者4.

所以只需要关注离他最近的这是个值。

为了方便,可以在set中存入两个正无穷以及两个负无穷!
注意:由于set不能含有相同的元素,所以两个“正无穷”不能相等

注意:0x3f3f3f3f不一定可以代表正无穷!他的值是1,061,109,567,大概是109,当可能存在的最大值大于109时,应该选择其他最大值,甚至是选择long long

part2 确定f[]

对于f[]的确定,代码不是太难,重点是要学会使用倍增求出。

f[i][j][k]表示从j出发,k先开车,走过2i所到达的位置。
k == 0表示小A开车

  1. 显然f[0][j][0] = ga[j]f[0][j][1] = gb[j]
  2. j==1,有(注意:与3不同的原因是21的一半不再是偶数,初始开车的人不同)
    f[1][j][0] = f[0][ f[0][j][0] ][1](先小A开一下,然后小B在小A开到的位置继续开。
    f[1][j][1] = f[0][ f[0][j][1] ][0]
  3. j > 1,有
    f[i][j][0] = f[i-1][ f[i-1][j][0] ][0]
    f[i][j][1] = f[i-1][ f[i-1][j][1] ][1]

f 的第一维的确定:由于使用倍增。而216=65,536所以17已经够使用。

part3 确定da[]db[]

同样需要找到转移关系。

da[i][j][k]表示从j出发,k先开车,走过2i,小A所走过的所有距离。
db[i][j][k]表示从j出发,k先开车,走过2i,小B所走过的所有距离。

与上面相同,分为以下三种情况:

  1. da[0][j][0] = dist(j, f[0][j][0]), db[0][j][0] = 0
    db[0][j][1] = dist(j, f[0][j][1]), da[0][j][1] = 0
  2. 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]
  3. i > 1,有:
    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]
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+5, M = 17;
const long long INF = 1e12;
long long h[N];
typedef long long ll;
typedef pair<ll, int> PLI;
set<pair<ll, int> >s;
ll ga[N], gb[N];
int f[M][N][2];
ll da[M][N][2];
ll db[M][N][2];
void init_g(int n)
{
s.insert(make_pair(INF, 0));
s.insert(make_pair(INF+1, 0));
s.insert(make_pair(-INF, 0));
s.insert(make_pair(-INF-1, 0));
for(int i = n; i; i--)
{
vector<PLI>v;
set<pair<ll, int> >::iterator it = s.lower_bound(make_pair(h[i], i));
it++;
for(int j = 1; j <= 4; j++) {
v.push_back(*it);
it--;
}
ll pmin1 = 0, pmin2 = 0;
ll min1 = INF, min2 = INF;
for(int j = 3; j >= 0; j--)
{
ll val = abs(v[j].first-h[i]);
ll pos = v[j].second;
if(val < min1)
{
min2 = min1;
pmin2 = pmin1;
min1 = val;
pmin1 = pos;
}
else if(val < min2)
{
min2 = val;
pmin2 = pos;
}
}
ga[i] = pmin2, gb[i] = pmin1;//注意可能最后ga[n-1],ga[n],gb[n-1],gb[n]存在问题
s.insert(make_pair((ll)h[i], i));
}
}
void init_f(int n)
{
for(int i = 0; i < M; i++)
{
for(int j = 1; j <= n; j++)
{
if(i == 0)
{
f[0][j][0] = ga[j];
f[0][j][1] = gb[j];
}
else if(i == 1)
{
f[1][j][0] = f[0][ f[0][j][0] ][1];
f[1][j][1] = f[0][ f[0][j][1] ][0];
}
else
{
f[i][j][0] = f[i-1][ f[i-1][j][0] ][0];
f[i][j][1] = f[i-1][ f[i-1][j][1] ][1];
}
}
}
}
inline ll dist(ll a, ll b)
{
return abs(h[a]-h[b]);
}
void init_d(int n)
{
for(int i = 0; i < M; i++)
{
for(int j = 1; j <= n; j++)
{
if(i == 0)
{
da[0][j][0] = dist(j, f[0][j][0]), db[0][j][0] = 0;
db[0][j][1] = dist(j, f[0][j][1]), da[0][j][1] = 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(ll p, ll x, ll &a, ll &b)
{
a = 0, b = 0;
for(int i = M-1; i >= 0; i--)
{
if(f[i][p][0] && a+b+da[i][p][0]+db[i][p][0] <= x)//从小A开始走
{
a += da[i][p][0];
b += db[i][p][0];
p = f[i][p][0];
}
}
}
int main()
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%lld", h+i);
init_g(n);
//for(int i = 1; i <= n; i++)
// cout << ga[i] << " " << gb[i] << "\n";
init_f(n);
init_d(n);
int x0;
int res = 0;
ll h_max = 0;
double rate_min = INF;//GUG位置:用了ll 。。。。。
scanf("%d", &x0);
for(int i = 1; i <= n; i++)
{
ll aa, bb;
calc(i, x0, aa, bb);
double rate = bb?(double)aa/bb :INF;
if(rate < rate_min || rate == rate_min &&h[i] > h_max)
{
rate_min = rate;
res = i;
h_max = h[i];
}
}
printf("%lld\n", res);
int m;
scanf("%d", &m);
while(m--)
{
ll s, x;
cin >> s >> x;
ll aa, bb;
calc(s, x, aa, bb);
printf("%d %d\n", aa, bb);
}
return 0;
}

AcWing\294. 计算重复

image

输入样例:

ab 2
acb 4
acb 1
acb 1
aa 1
aaa 3
baab 1
baba 11
aaaaa 1
aaa 20

输出样例:

2
1
4
7
12

这一道题目简单概括就是求一个最大的m,使得字符串s2被复制m*n2之后是字符串s1*n1的子序列。

更进一步进行简化:可以认为求一个最大的t,使得s2复制t次后是s1复制n1次的子序列。
m=tn2.

这是因为满足单调性:当复制t次是子序列,那么当小于t次,显然也是子序列。
当复制t+1次已经不是子序列,那么大于t+1时,同样不再是子序列。(因为有多余的在s1*n1无法找到)

在这里,首先认为s1*INF是无限多的。由于具有周期性,可以使用0—size-1的位置表示。
f[i][j].指从第i个位置开始,得到2j个重复的s2的子序列所需要的最短的长度。

值得一提的是要注意有无解的情况

#include <bits/stdc++.h>
using namespace std;
char s1[120], s2[120];
int n1, n2;
typedef long long ll;
#define M 31
ll f[120][M];
int main()
{
while(scanf("%s%d%s%d", s2, &n2, s1, &n1) != EOF)//由于具有取模操作,所以两个字符串从0开始计算
{
//暴力枚举f[i][0]
bool fault = false;//无解情况
int len1 = strlen(s1), len2 = strlen(s2);
for(int i = 0; i < len1; i++)
{
f[i][0] = 0;
int p = i;//在s1中的位置指针
for(int j = 0; j < len2; j++)
{
int cnt = 0;
while(s1[p] != s2[j])
{
p = (p+1)%len1;
cnt++;
if(cnt >= len1) {fault = true; break;}
}
if(fault) break;
f[i][0] += 1+cnt;
p = (p+1)%len1;
}
if(fault) break;
}
if(fault){
puts("0");
continue;
}
//for(int i = 0; i < len1; i++) printf("%d\t", f[i][0]);
//初始化倍增DP
for(int j = 1; j < M; j++)
{
for(int i = 0; i < len1; i++)
{
f[i][j] = f[i][j-1] + f[ (i+f[i][j-1])%len1 ][j-1];
}
}
ll ans = 0;
//进行拼凑
for(int i = 0; i < len1; i++)
{
ll t = 0, p = i;
for(int j = M-1; j >= 0; j--)
{
if(p + f[p%len1][j] > len1*n1) continue;
p += f[p%len1][j];
t += 1 << j;
}
ans = max(ans, t);
}
printf("%d", ans/n2);
}
return 0;
}
/*
格外注意:我的f[][]数组的第一位的取值范围仅仅可以是0~len-1
*/
posted @   心坚石穿  阅读(61)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示