ヾ(o◕∀◕)ノヾ各种动态规划经典例题(状压DP,倍增DP)

本文介绍了四道算法竞赛题目,涉及动态规划和倍增优化的解决方案。第一题是关于计算从起点到终点的路径方案数,避免厄运数字;第二题是关于偶像乐队重新排列的最少交换次数;第三题是计算食堂做菜的最短时间,考虑不同人的忍受度;第四题是寻找最节省时间的跑路策略。这些题目展示了动态规划在解决复杂问题中的灵活性和效率,以及倍增优化在减少计算量上的优势。
摘要由CSDN通过智能技术生成

ヾ(o◕∀◕)ノヾ各种动态规划经典例题(状压DP,倍增DP)

一、yyy loves Maths VII

题目背景

yyy 对某些数字有着情有独钟的喜爱,他叫他们为幸运数字;然而他作死太多,所以把自己讨厌的数字成为“厄运数字”。

题目描述

一群同学在和 yyy 玩一个游戏。

每次,他们会给 yyy n 张卡片,卡片上有数字,所有的数字都是“幸运数字”,我们认为第 ii 张卡片上数字是 ai

每次 yyy 可以选择向前走 ai 步并且丢掉第 i 张卡片。当他手上没有卡片的时候他就赢了。

但是呢,大家对“厄运数字”的位置布置下了陷阱,如果 yyy 停在这个格子上,那么他就输了。注意:即使到了终点,但是这个位置是厄运数字,那么也输了。

现在,有些同学开始问:yyy 有多大的概率会赢呢?

大家觉得这是个好问题,有人立即让 yyy 写个程序:“电脑运行速度很快!2424 的阶乘也不过就 620448401733239439360000,yyy 你快写个程序来算一算。”

yyy 表示很无语,他表示他不想算概率,最多算算赢的方案数,而且是对 10^9+7109+7 取模后的值。

大家都不会写程序,只好妥协。

但是这时候 yyy 为难了,24! 太大了,要跑好长时间。

他时间严重不够!需要你的帮助!

由于 yyy 人格分裂,某个数字可能既属于幸运数字又属于厄运数字。

输入格式

第一行一个整数 n

下面一行 n 个整数,第 i 个整数代表第 ii 张卡片上的数字 ai

第三行 m 表示 yyy 的厄运数字个数(最多 2 个)。

输出格式

输出胜利方案数对 10^9+7 取模的结果。

输入输出样例

输入 #1

8
1 3 1 5 2 2 2 3
0

输出 #1

40320

输入 #2

24
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
2
10 15

输出 #2

0

说明/提示

  • 10% 的数据 n≤10;
  • 50% 的数据 n≤23;
  • 100% 的数据 n≤24。

状压,思路是一个二进制24位的数,每位数是1就说明用过了,0就是没用过。两个数组,一个数组是dis, 算距离,看看会不会碰到厄运数字。一个数组是f,状态转移方案数用。当使用i牌的距离正好是厄运数字时,我们就不能把i的情况累积起来了,所以就直接跳过。其他时候算f[i]的方案数就是f[i] 少走一步的所有方案数相加就行了。

AC code

#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
#define M 1000000007
using namespace std;
//速读
int dis[1 << 24], f[1 << 24];
int a[2];
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

int main()
{
    ios::sync_with_stdio(false);
    int n = r;
    for(re int i = 0; i < n; i++){
        dis[1 << i] = r;
    }
    int m = r;
    for(re int i = 0; i < m; i++){
        a[i] = r;
    }
    f[0] = 1;
    int mx = (1 << n) - 1;
    for(re int i = 1; i <= mx; i++){
        dis[i] = dis[i ^ ((i) & (-i))] + dis[((i) & (-i))];
        if(dis[i] == a[1] || dis[i] == a[0]){
            continue;
        }
        for(re int j = i, k = ((j) & (-j)); j; j ^= k, k = ((j) & (-j))){
            f[i] += f[i^k];
            f[i] %= M;
        }
    }
    cout<<f[mx]<<endl;
    return 0;
}

二、邦邦的大合唱站队

题目背景

BanG Dream!里的所有偶像乐队要一起大合唱,不过在排队上出了一些问题。

题目描述

N个偶像排成一列,他们来自M个不同的乐队。每个团队至少有一个偶像。

现在要求重新安排队列,使来自同一乐队的偶像连续的站在一起。重新安排的办法是,让若干偶像出列(剩下的偶像不动),然后让出列的偶像一个个归队到原来的空位,归队的位置任意。

请问最少让多少偶像出列?

输入格式

第一行2个整数N,M。

接下来N个行,每行一个整数a(1≤aiM),表示队列中第i个偶像的团队编号。

输出格式

一个整数,表示答案

输入输出样例

输入 #1

12 4
1
3
2
4
2
1
2
3
1
1
3
4

输出 #1

7

说明/提示

【样例解释】

1  33  3
2  34  4
2  41  22  2
3  21  1
1  1
3  14  1

【数据规模】

对于20%的数据,N≤20,M=2

对于40%的数据,N≤100,M≤4

对于70%的数据,N≤2000,M≤10

对于全部数据,1≤N≤10^5,M≤20

状压DP, 注释很详细了

AC code

#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
using namespace std;
int n, m, x, f[2000000];
int s[100010][30], num[30], sm[2000000];

void dfs(int x, int s, bool b){
    if(x == m){
        return;
    }
    //如果要排队
    if(b){
        //s排上x的总人数为原来人数加上x的人数
        sm[s | (1 << x)] = sm[s] + num[x + 1];
        dfs(x + 1, s | (1 << x), 0);
        dfs(x + 1, s | (1 << x), 1);
    }else{
        dfs(x + 1, s, 0);
        dfs(x + 1, s, 1);
    }
}
bool d(int s,int x){
	return (s&(1<<(x-1)));
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> n >> m;
    //记录数据
    for(int i = 1; i <= n; i++){
        cin >> x;
        for(int j = 1; j <= m; j++){
            s[i][j] = s[i - 1][j];
        }
        //算x队的总和
        num[x]++;
        //算x队前i里面有多少人
        s[i][x]++;
    }
    //搜索,算出如果我们排队状态为s的话,总和人数sm
    dfs(0, 0, 0);
    dfs(0, 0, 1);
    //初始化到最大,开始动态规划
    memset(f,0x3f,sizeof(f));
    f[0] = 0;
    //遍历各种状态
    for(int i = 1; i < (1 << m); i++){
        //遍历每一组
        for(int j = 1; j <= m; j++){
            //如果这个组在这个状态里需要
            if(d(i, j)){
                //左边界为不排j的总人数, 右边界为所有总人数
                int l = sm[i ^ (1 << (j - 1))], r = sm[i];
                //计算方法:不排j总方案 + j的人数, 去掉已经在队列中的
                f[i] = min(f[i], f[i ^ (1 << (j - 1))] + (r - l) - (s[r][j] - s[l][j]));
            }
        }
    }
    cout << f[(1 << m) - 1] << endl;
    return 0;
}

三、[SDOI2009]学校食堂

题目描述

小F 的学校在城市的一个偏僻角落,所有学生都只好在学校吃饭。学校有一个食堂,虽然简陋,但食堂大厨总能做出让同学们满意的菜肴。当然,不同的人口味也不一定相同,但每个人的口味都可以用一个非负整数表示。 由于人手不够,食堂每次只能为一个人做菜。做每道菜所需的时间是和前一道菜有关的,若前一道菜的对应的口味是a,这一道为b,则做这道菜所需的时间为(a or b)-(a and b),而做第一道菜是不需要计算时间的。其中,or 和and 表示整数逐位或运算及逐位与运算,C语言中对应的运算符为“|”和“&”。

学生数目相对于这个学校还是比较多的,吃饭做菜往往就会花去不少时间。因此,学校食堂偶尔会不按照大家的排队顺序做菜,以缩短总的进餐时间。

虽然同学们能够理解学校食堂的这种做法,不过每个同学还是有一定容忍度的。也就是说,队伍中的第i 个同学,最多允许紧跟他身后的Bi 个人先拿到饭菜。一旦在此之后的任意同学比当前同学先拿到饭,当前同学将会十分愤怒。因此,食堂做菜还得照顾到同学们的情绪。 现在,小F 想知道在满足所有人的容忍度这一前提下,自己的学校食堂做完这些菜最少需要多少时间。

输入格式

第一行包含一个正整数C,表示测试点的数据组数。 每组数据的第一行包含一个正整数N,表示同学数。 每组数据的第二行起共N行,每行包含两个用空格分隔的非负整数Ti和Bi,表示按队伍顺序从前往后的每个同学所需的菜的口味和这个同学的忍受度。 每组数据之间没有多余空行。

输出格式

包含C行,每行一个整数,表示对应数据中食堂完成所有菜所需的最少时间。

输入输出样例

输入 #1

255 24 112 03 32 225 04 0

输出 #1

161

说明/提示

对于第一组数据:

同学1允许同学2或同学3在他之前拿到菜;同学2允许同学3在他之前拿到菜;同学3比较小气,他必须比他后面的同学先拿菜……

一种最优的方案是按同学3、同学2、同学1、同学4、同学5做菜,每道菜所需的时间分别是0、8、1、6及1。

【数据规模和约定】

对于30%的数据,满足1 ≤ N ≤ 20。

对于100%的数据,满足1 ≤ N ≤ 1,000,0 ≤ Ti ≤ 1,000,0 ≤ Bi ≤ 7,1 ≤ C ≤ 5。

存在30%的数据,满足0 ≤ Bi ≤ 1。

存在65%的数据,满足0 ≤ Bi ≤ 5。

存在45%的数据,满足0 ≤ Ti ≤ 130。

这题的状态明星就是打菜与否,我们需要存储一个人后面八个人是否打菜的情况。

整个dp数组三个参数,第一个是第i个人的意思,第二个就是状态,也就是包括第i个人在内后面8个人的情况,最后一个参数是k,代表目前排名最靠后的打了菜的人,状态转移方程有两个。假设我们现在考虑的状态是第i个人本人已经打菜了,那可以直接转移给第i+1个人来判断。如果这个人没打菜,那我们就遍历这个人后面的七个人,看能不能先打菜,然后要不断考虑每个人的忍耐度,每次往后扫描的时候都要更新。

AC code

#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
using namespace std;
int n, T[1005], B[1005], f[1005][1 << 8][20];
int main()
{
    ios::sync_with_stdio(false);
    int C;
    cin >> C;
    while(C--){
        cin >> n;
        for(int i = 1; i <= n; i++){
            cin >> T[i] >> B[i];
        }
        memset(f, 0x3f3f3f3f, sizeof(f));
        f[1][0][7] = 0;
        for(int i = 1; i <= n; i++){
            for(int j = 0; j < (1 << 8); j++){
                for(int k = -8; k <= 7; k++){
                    if(f[i][j][k+8] != 0x3f3f3f3f){
                        //如果这个人本身已经打过饭了
                        if(j & 1){
                            //直接转移即可
                            f[i + 1][j >> 1][k + 7] = min(f[i + 1][j >> 1][k + 7], f[i][j][k + 8]);
                        }
                        else{
                            //计算忍耐值
                            int lir = 0x3f3f3f3f;
                            //否则,遍历看让他后面谁打饭
                            for(int h = 0; h <= 7; h++){
                                if(!((j >> h) & 1)){
                                    if(i + h > lir){
                                        break;
                                    }
                                    lir = min(lir, i + h + B[i + h]);
                                    f[i][j | (1 << h)][h + 8] = min(f[i][j | (1 << h)][h + 8], f[i][j][k + 8] + (i + k ? (T[i +k] ^ T[i + h]) : 0));
                                }
                            }
                        }
                    }
                }
            }
        }
        int res = 0x3f3f3f3f;
        for(int k = 0; k <= 8; k++){
            res = min(res, f[n +1][0][k]);
        }
        cout<<res<<endl;
    }
    return 0;
}

四、跑路

题目描述

小A的工作不仅繁琐,更有苛刻的规定,要求小A每天早上在6:00之前到达公司,否则这个月工资清零。可是小A偏偏又有赖床的坏毛病。于是为了保住自己的工资,小A买了一个十分牛B的空间跑路器,每秒钟可以跑2^k千米(k是任意自然数)。当然,这个机器是用longint存的,所以总跑路长度不能超过maxlongint千米。小A的家到公司的路可以看做一个有向图,小A家为点1,公司为点n,每条边长度均为一千米。小A想每天能醒地尽量晚,所以让你帮他算算,他最少需要几秒才能到公司。数据保证1到n至少有一条路径。

输入格式

第一行两个整数n,m,表示点的个数和边的个数。

接下来m行每行两个数字u,v,表示一条u到v的边。

输出格式

一行一个数字,表示到公司的最少秒数。

输入输出样例

输入 #1

4 41 11 22 33 4

输出 #1

1

说明/提示

【样例解释】

1->1->2->3->4,总路径长度为4千米,直接使用一次跑路器即可。

【数据范围】

50%的数据满足最优解路径长度<=1000;

100%的数据满足n<=50,m<=10000,最优解路径长度<=maxlongint。

倍增优化动态规划

这题的题目还需要多读。算法有三个步骤,第一步是初始化,每个点要初始化为无法到达(后面用Floyd算法)然后每个接收的点距离为1。第二步是计算,算出每两个点之间有没有直达道路,有的话设为1。第三步就是在第二步的基础上用Floyd算法,算出最短路径。倍增优化就体现在第二步,直接算出很多距离。

AC code

#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int dis[60][60];
bool G[60][60][65];
int main()
{
    ios::sync_with_stdio(false);
    memset(G,false,sizeof(G));
    memset(dis,10,sizeof(dis));
    int n = r;
    int m = r;
    for(int i = 1; i <= m; i++){
        int x = r;
        int y = r;
        dis[x][y] = 1;
        G[x][y][0] = true;
    }
    for(int k = 1; k <= 64; k++){
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= n; j++){
                for(int t = 1; t <= n; t++){
                    if(G[i][j][k-1] && G[j][t][k-1]){
                        G[i][t][k] = true;
                        dis[i][t] = 1;
                    }
                }
            }
        }
    }
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            if(i != j){
                for(int t = 1; t <= n; t++){
                    if(i != t && j != t){
                        dis[j][t] = min(dis[j][t], dis[j][i] + dis[i][t]);
                    }
                }
            }
        }
    }
    cout<<dis[1][n];
    return 0;
}

五、[NOIP2012 提高组] 开车旅行

题目描述

小 A 和小 B 决定利用假期外出旅行,他们将想去的城市从 1 到 n 编号,且编号较小的城市在编号较大的城市的西边,已知各个城市的海拔高度互不相同,记城市 i 的海拔高度为hi,城市 i 和城市 j 之间的距离 di,j 恰好是这两个城市海拔高度之差的绝对值,即 di,j=∣hihj∣。

旅行过程中,小 A 和小 B 轮流开车,第一天小 A 开车,之后每天轮换一次。他们计划选择一个城市 s 作为起点,一直向东行驶,并且最多行驶 x 公里就结束旅行。

小 A 和小 B 的驾驶风格不同,小 B 总是沿着前进方向选择一个最近的城市作为目的地,而小 A 总是沿着前进方向选择第二近的城市作为目的地(注意:本题中如果当前城市到两个城市的距离相同,则认为离海拔低的那个城市更近)。如果其中任何一人无法按照自己的原则选择目的城市,或者到达目的地会使行驶的总距离超出 x 公里,他们就会结束旅行。

在启程之前,小 A 想知道两个问题:

1、 对于一个给定的 x=x0,从哪一个城市出发,小 A 开车行驶的路程总数与小 B 行驶的路程总数的比值最小(如果小 B 的行驶路程为 0,此时的比值可视为无穷大,且两个无穷大视为相等)。如果从多个城市出发,小 A 开车行驶的路程总数与小 B 行驶的路程总数的比值都最小,则输出海拔最高的那个城市。

2、对任意给定的 x=xi 和出发城市 si,小 A 开车行驶的路程总数以及小 B 行驶的路程总数。

输入格式

第一行包含一个整数 n,表示城市的数目。

第二行有 n 个整数,每两个整数之间用一个空格隔开,依次表示城市 1 到城市 n 的海拔高度,即 h1,h2…hn,且每个 hi 都是互不相同的。

第三行包含一个整数 x0。

第四行为一个整数 m,表示给定 msixi

接下来的 m 行,每行包含 2 个整数 sixi,表示从城市si 出发,最多行驶 xi 公里。

输出格式

输出共 m+1 行。

第一行包含一个整数 s0,表示对于给定的 x0,从编号为 s0 的城市出发,小 A 开车行驶的路程总数与小 B 行驶的路程总数的比值最小。

接下来的 m 行,每行包含 2 个整数,之间用一个空格隔开,依次表示在给定的 sixi 下小 A 行驶的里程总数和小 B 行驶的里程总数。

输入输出样例

输入 #1

4 2 3 1 4 3 4 1 3 2 3 3 3 4 3

输出 #1

1 1 1 2 0 0 0 0 0 

输入 #2

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

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

说明/提示

【样例1说明】

img

各个城市的海拔高度以及两个城市间的距离如上图所示。

如果从城市 1 出发,可以到达的城市为 2,3,4,这几个城市与城市 1 的距离分别为 1,1,2,但是由于城市 3 的海拔高度低于城市 2,所以我们认为城市 3 离城市 1 最近,城市 2 离城市 1 第二近,所以小A会走到城市 2。到达城市 2 后,前面可以到达的城市为 3,4,这两个城市与城市 2 的距离分别为 2,1,所以城市 4 离城市 2 最近,因此小B会走到城市4。到达城市 4 后,前面已没有可到达的城市,所以旅行结束。

如果从城市 2 出发,可以到达的城市为 3,4,这两个城市与城市 2 的距离分别为 2,1,由于城市 3 离城市 2 第二近,所以小 A 会走到城市 3。到达城市 3 后,前面尚未旅行的城市为 4,所以城市 4 离城市 3 最近,但是如果要到达城市 4,则总路程为 2+3=5>3,所以小 B 会直接在城市 3 结束旅行。

如果从城市 3 出发,可以到达的城市为 4,由于没有离城市 3 第二近的城市,因此旅行还未开始就结束了。

如果从城市 4 出发,没有可以到达的城市,因此旅行还未开始就结束了。

【样例2说明】

x=7 时,如果从城市 1 出发,则路线为 1→2→3→8→9,小 A 走的距离为 1+2=3,小 B 走的距离为 1+1=2。(在城市 1 时,距离小 A 最近的城市是 2 和 6,但是城市 2 的海拔更高,视为与城市 1 第二近的城市,所以小 A 最终选择城市 2;走到9 后,小 A 只有城市 10 可以走,没有第二选择可以选,所以没法做出选择,结束旅行)

如果从城市 2 出发,则路线为 2→6→7,小 A 和小 B 走的距离分别为 2,4。

如果从城市 3 出发,则路线为 3→8→9,小 A 和小 B 走的距离分别为2,1。

如果从城市 4 出发,则路线为 4→6→7,小 A 和小 B 走的距离分别为 2,4。

如果从城市 5 出发,则路线为 5→7→8,小 A 和小 B 走的距离分别为 5,1。

如果从城市 6 出发,则路线为 6→8→9,小 A 和小 B 走的距离分别为5,1。

如果从城市 7 出发,则路线为 7→9→10,小 A 和小 B 走的距离分别为2,1。

如果从城市 8 出发,则路线为 8→10,小 A 和小 B 走的距离分别为2,0。

如果从城市 9 出发,则路线为 9,小 A 和小 B 走的距离分别为 0,0(旅行一开始就结束了)。

如果从城市 10 出发,则路线为 10,小 A 和小 B 走的距离分别为0,0。

从城市 2 或者城市 4 出发小 A 行驶的路程总数与小 B 行驶的路程总数的比值都最小,但是城市 2 的海拔更高,所以输出第一行为 2。

【数据范围与约定】

对于 30% 的数据,有1≤n≤20,1≤m≤20;
对于40% 的数据,有1≤n≤100,1≤m≤100;
对于 50% 的数据,有1≤n≤100,1≤m≤1000;
对于 70% 的数据,有1≤n≤1000,1≤m≤10^4;
对于 100% 的数据:1≤n,m≤105,−109≤hi​≤10^9,1≤si​≤n ,0≤xi​≤10^9
数据保证 hi​ 互不相同。

这道题代码量比较大,具体的代码逻辑注释也比较清楚,这里做一个思路的大致复盘。首先是接收数据,三个数组,一个是城市海拔。另外两个是后面的问题。然后是初始化,用平衡树,就是找出每个点的最近点和次进点,需要比较的是前驱,后继,前驱的前驱,后继的后继,这里还用到了multiset,可以插入重复元素的集合,也是自动排序的平衡二叉树。找出这些点之后,存在dp数组中,有三个dp数组,走的天数是倍增的,所以一开始先初始化一个距离。然后后面就是dp,三个算式,代码里都有。然后是解决问题。这里要先有一个函数计算从x成走s路程的情况下,a,b走的最远距离。这样这个函数就直接解决了问题2。对于问题1,就是遍历每个城市作为起点,比较找答案。

AC code

#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
#define r read()
using namespace std;
//速读
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
// 数据接收
const int N = 1e5 + 200, INF = 2e9;
int n, m, x0;
int h[N], s[N], x[N];
void getData(){
        cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> h[i];
    }
    cin >> x0 >> m;
    for(int i = 1; i <= m; i++){
        cin >> s[i] >> x[i];
    }
}
// 预处理 平衡树
struct City{
    // 编号和海拔
    int id, al;
    // 重载运算符,按照海拔升序
    friend bool operator < (City a, City b){
        return a.al < b.al;
    }
};
//倍增优化dp
//数组分析
int f[20][N][5];     // 从城市i出发,行驶2^i天,k先开车,最终会到达的城市
int da[20][N][5];    // 从城市j出发,形式2^i天,k先开车,小A行驶的路程长度
int db[20][N][5];    // 从城市j出发,形式2^i天,k先开车,小B行驶的路程长度

// 支持集合内重复元素的set
// 建树的一个思路记录 这道题我们要找距离一个点最近的点和次进的点
// 构建平衡树之后,最近的点就是这个点的前驱或者后继,次进的点就是前驱或者后继或者前驱的前驱或者后继的后继
// 所以我们一开始算第一个点的时候,就要先初始化开头和结尾是死路,防止越界
multiset<City> q;
void init(){
    // 初始化成死路
    h[0] = INF, h[n+1] = -INF;
    //设置初始城市
    City st;
    //初始化四个终点
    st.id = 0;
    st.al = INF;
    q.insert(st), q.insert(st);
    st.id = n + 1;
    st.al = -INF;
    q.insert(st), q.insert(st);
    for(int i = n; i; i--){
        // 从当前城市出发的话,a,b的下一站
        int ga, gb;
        City now;
        now.id = i, now.al = h[i];
        q.insert(now);
        //迭代器,在这里指向now节点
        set<City>::iterator p = q.lower_bound(now);
        //前驱
        p--;
        int lt = (*p).id, lh = (*p).al;
        //后继
        p++, p++;
        int ne = (*p).id, nh = (*p).al;
        p--;
        //如果前驱更近
        if(abs(nh - h[i]) >= abs(h[i] - lh)){
            gb = lt;
            p--, p--;
            //如果前驱的前驱更近
            if(abs(nh - h[i]) >= abs(h[i] - (*p).al)){
                ga = (*p).id;
            }else{
                ga = ne;
            }
        }
        //如果后继更近
        else{
            gb = ne;
            p++, p++;
            //如果前驱更近
            if(abs((*p).al - h[i]) >= abs(h[i] - lh)){
                ga = lt;
            }else{
                ga = (*p).id;
            }
        }
        //dp数组的预处理
        f[0][i][0] = ga,f[0][i][1] = gb;
		da[0][i][0] = abs(h[i] - h[ga]);
		db[0][i][1] = abs(h[i] - h[gb]);
    }
}

void dp(){
    for(int i = 1; i <= 18; i++){
        for(int j = 1; j <= n; j++){
            for(int k = 0; k < 2; k++){
                if(i == 1){
                    f[1][j][k] = f[0][f[0][j][k]][1-k];
					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{
                    // f[i-1][j][k]  相当于是中点城市
                    f[i][j][k] = f[i-1][f[i-1][j][k]][k];
                    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];
                }
            }
        }
    }
}
//求解问题1
int la,lb,ansid;
// 求解从城市 S 出发,最多行驶X公里,小A和小B分别行驶了多少距离
void calc(int S,int X)
{
	int p = S;
	la = 0, lb = 0;
	for(int i = 18; i >= 0; i--){
        if(f[i][p][0] && la + lb + da[i][p][0] + db[i][p][0] <= X){
            la += da[i][p][0];
            lb += db[i][p][0];
            //倍增前进
            p = f[i][p][0];
        }
	}
}
double ans = INF * 1.0;
void q1(){
    for(int i = 1; i <= n; i++){
        calc(i, x0);
        double nowAns = double(la) / double (lb);
        if(nowAns < ans){
            ans = nowAns;
            ansid = i;
        }
        else{
            if(nowAns == ans && h[ansid] < h[i]){
                ansid = i;
            }
        }
    }
    cout<<ansid<<endl;
}
void q2(){
    for(int i = 1; i <= m; i++){
        //按照上面的描述,不断求解每个城即可
        calc(s[i], x[i]);
        cout<<la << " " << lb<<endl;
    }
}
int main()
{
    ios::sync_with_stdio(false);
    getData();
    init();
    dp();
    q1();
    q2();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值