算法竞赛进阶指南 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 的海拔高度为 H i H_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,大概是 1 0 9 10^9 109,当可能存在的最大值大于 1 0 9 10^9 109时,应该选择其他最大值,甚至是选择long long

part2 确定f[]

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

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

  1. 显然f[0][j][0] = ga[j]f[0][j][1] = gb[j]
  2. j==1,有**(注意:与3不同的原因是 2 1 2^1 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 的第一维的确定:由于使用倍增。而 2 16 = 65 , 536 2^{16} =65,536 216=65,536所以17已经够使用。

part3 确定da[]db[]

同样需要找到转移关系。

da[i][j][k]表示从j出发,k先开车,走过 2 i 2^i 2i,小A所走过的所有距离。
db[i][j][k]表示从j出发,k先开车,走过 2 i 2^i 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 = ⌊ t n 2 ⌋ m=\lfloor \frac{t}{n2}\rfloor m=n2t.

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

在这里,首先认为s1*INF是无限多的。由于具有周期性,可以使用0—size-1的位置表示。
f[i][j].指从第i个位置开始,得到 2 j 2^j 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
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值