acwing周赛第8场题解

1.最小消耗(简单)

传送门
有 n 个怪兽等待你去消灭。

怪兽共分为两种形态,不妨用 0 和 1 来表示。

消灭一个 0 形态的怪兽需要耗费的法力值为 a。

消灭一个 1 形态的怪兽需要耗费的法力值为 b。

你还可以使用改造魔法将 0 形态怪兽改造为 1 形态或将 1 形态怪兽改造为 0 形态。

改造一个怪兽需要耗费的法力值为 c。

请问,将怪兽全部消灭最少需要耗费多少法力值。

输入格式
第一行包含整数 T,表示共有 T 组测试数据。

每组数据第一行包含四个整数 n,a,b,c。

第二行包含一个长度为 n 的 01 字符串,其中的第 i 个字符表示第 i 个怪兽的初始形态。

输出格式
每组数据结果占一行,输出一个整数表示最小消耗。

数据范围
前三个测试点满足 1≤n≤20。
全部测试点满足 1≤T≤10,1≤n,a,b,c≤1000。

输入样例:
6
3 1 1 1
100
5 10 100 1
01010
5 10 1 1
11111
5 1 10 1
11111
12 2 1 10
101110110101
2 100 1 10
00
输出样例:
3
52
5
10
16
22
难度:简单
时/空限制:1s / 256MB
总通过数:940
总尝试数:1196
来源:AcWing,第8场周赛

1.1 解题思路

我们可以改变怪兽的类型,输出消灭所有怪兽的最小代价,先判断怪兽类型,,然后取求修改怪兽类型用的代价 + 消灭修改后的代价或不修改直接消灭的代价的最小值。即 ans += min(a + c, b), 或 ans += min(a, b + c);

1.2 参考代码

#include<bits/stdc++.h>

using namespace std;

int n, t, a, b, c;
string s;

int main()
{
    cin >> t;
    while (t--) {
        cin >> n >> a >> b >> c;
        cin >> s;
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            if (s[i] == '0') ans += min(a, b + c); 
            else ans += min(b, a + c);
        }
        cout << ans << endl;
    }
    return 0;
}

2.选取石子(中等)

传送门
给定 n 个石子,编号为 1∼n。

其中第 i 个石子的价值为 ai。

你需要从中任意挑选若干个石子,并将挑选好的石子按照编号从小到大的顺序排成一排。

选中的石子在排好序后需要满足,对于任意两个相邻的石子(不妨设它们的编号为 x,y),x−y=ax−ay 均成立。

例如,当有 n=8 个石子,石子价值分别为 [3,4,4,6,6,7,8,9] 时,一些合理的选择方案如下:

选择 1,2,4 号石子,它们的价值分别为 3,4,6。1 号石子与 2 号石子相邻,2−1=4−3 成立。2 号石子与 4 号石子相邻,4−2=6−4 成立。所以方案合理。
选择 7 号石子。可以只选择一个石子,此时选取任何石子均为合理方案。
你的选择方案不仅需要合理,而且还要使得选中石子的价值总和尽可能大。

请计算并输出价值总和的最大可能值。

输入格式
第一行包含整数 n。

第二行包含 n 个整数 a1,a2,…,an。

输出格式
一个整数,表示选中石子的价值总和的最大可能值。

数据范围
前三个测试点满足 1≤n≤10。
全部测试点满足 1≤n≤2×105,1≤ai≤4×105。

输入样例1:
6
10 7 1 9 10 15
输出样例1:
26

2.1 解题思路

2.1.1 题目取石子是根据等式 x−y=ax−ay ==> x-ax = y-ay, 其中 x 和 ax 是有对应关系(下标对应值),可以根据 x - ax的差值来进行分组, 然后开一个sum数组来进行统和,sum[i - a[i]] += a[i](下标=x-ax, sum[x-ax]存入a[x] ,最后遍历sum数组取一个最大值输出。
2.1.2 观察题目给的数据范围, 1≤n≤2×105,1≤ai≤4×105。
可以推出x - ax的差值的范围是(-4e5, 4e5)内的,一共有最多8e5中可能。
观察数据,可以构造出2e5个1e5相加, 最大值 = 2e10的情况,会爆int,所以sum数组要开long long。
所以,sum数组开的下,但 x-ax可能是一个负值,当下标使用时会越界,所以要加上一个偏移量N(N = 4e5)即sum[i-a[i] + N] += a[i],来保证sum数组的下标是正的。
时间复杂度,输入a[i]的同时进行统计sum[i-a[i] + N] += a[i], 时间复杂度是O(n)的,遍历sum数组取最大值,时间复杂度O(n), 总时间复杂度O(n), 具有可行性。

2.2 参考代码

#include<bits/stdc++.h>

using namespace std;

const int N = 2e5 + 7, M = 8e5 + 7;

int a[N];
long long s[M];  //求和的sum数组

int n;

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        s[i - a[i] + 400000] += a[i];//根据x - ax 进行分组求和
    }
    long long ans = 0; 
    for (int i = 1; i < M; ++i) { 
        ans = max(ans, s[i]); // 求最大值
    }
    cout << ans << endl;
    return 0;
}

3.更新线路 (困难)

传送门

给定一个 n 个点 m 条边的有向强连通图。

点的编号为 1∼n,边的长度均为 1。

给定一条由点 s 到点 t 的简单路径 p1,p2,…,pk,其中 p1=s,pk=t。

注意,这条路经不一定是从点 s 到点 t 的最短路径。

现在,小明要沿着这条路径从点 s 走到点 t。

在他的行进过程中,手机上的导航软件将持续为他导航,持续为他提供最短行进线路建议。

当然,他并不一定会采纳这些建议,因为他一定会沿着之前给定的线路行进。

设想一下,在行进中,导航软件的工作过程。

首先,在点 s 处,导航软件会找到并显示出一条从点 s 到点 t 的最短路径。

如果小明的行进线路恰好与软件推荐线路一致,则软件推荐线路将不会发生任何改变。

但是,如果小明在某一点处,行进线路与软件推荐线路发生了分歧,例如,软件推荐前往点 v,小明却前往了点 w。

那么,在他到达点 w 后,软件就会实时更新推荐线路,即找到并显示出一条从点 w 到点 t 的最短路径。

导航软件会一直工作到小明到达点 t 为止,在这一过程中,软件的提供线路可能会经过若干次更新。

例如,给定一个有向强连通图,如下所示:

在这里插入图片描述

给出的简单路径为 1,2,3,4

那么,小明从点 1 出发,导航软件找到并显示出一条从点 1 到点 4 的最短路径,这样的路径只有一条 [1,5,4]。

小明并未听从软件的建议,坚持到达了点 2,此时软件推荐线路实时更新,提供出一条点 2 到点 4 的最短路径,例如 [2,6,4](注意,软件提供的最短路径也有可能是 [2,3,4])。

小明还是不听软件的建议,坚持到达了点 3,此时软件推荐线路再次更新,提供出一条点 3 到点 4 的最短路径,即 [3,4]。

最后,小明沿软件提供路线,到达目的地点 4,软件完成导航。

总的来看,软件推荐线路发生了两次更新。

值得注意的是,如果软件在第一次更新推荐线路时,给出的最短路径为 [2,3,4],则小明将按照推荐线路走到终点,软件将无需再次更新推荐线路。

也就是说,由于软件在推荐最短路径时具有随机性,所以在整个行进过程中,软件更新推荐线路的次数并不确定。

现在,给定有向图和行进路线,请你求出软件更新推荐线路的最小可能次数和最大可能次数。

输入格式
第一行包含两个整数 n 和 m。

接下来 m 行,每行包含两个整数 u,v,表示存在一条从点 u 到点 v 的有向边。

随后一行包含一个整数 k。

最后一行包含 k 个整数 p1,p2,…,pk。

输出格式
一行,空格隔开的两个整数,表示软件更新推荐路线的最小可能次数和最大可能次数。

数据范围
前三个测试点满足 1≤n≤10。
全部测试点满足 2≤n≤m≤2×105,1≤u,v≤n,u≠v,2≤k≤n,1≤pi≤n,pi 两两不同。
保证输入没有重边,但是可能同时存在端点相同方向不同的两条边,例如 (a,b) 和 (b,a)。

输入样例1:
6 9
1 5
5 4
1 2
2 3
3 4
4 1
2 6
6 4
4 2
4
1 2 3 4
输出样例1:
1 2

3.1 解题思路

重点:反向建图 + bfs + 思维
从 s 到 t 按规定走法,在行走过程中如果当前点到 t 的最短路径 > 上一点到 t 最短路径时,就必有导航提示。因为导航提示的时间具有随机性,题目要求输出导航次数的最小值和最大值。
最小值是当前点不在最短路径上时,提示+ 1.
最大值是当前点在最短路径上,但从当前点到 t 点的路径数 > 1 或是当前点不在最短路径上时,提示+ 1.
求最短路径可以用最短路算法或bfs,这题的边权都是1,就可以用bfs求最短路(更好写)。这题是求从s到t中按已经确定的路径进行行走,然后进行导航推荐的最小次数和最大次数。
按照思路跑最短路时,每次都是跑从某个点到 t 的最短路径,每次的终点都是一样的,因此要跑的最短路次数会很多。 因为每次都时求每个点到t的最短路径,那么反过来等价于求t到任意点的最短路径(就只用跑一次啦 )。求t到任意点的最短路径,首先要反向建图, 然后跑bfs求最短路并记录最短路径数量

3.2 参考代码

#include<bits/stdc++.h>

using namespace std;

const int N = 2e5 + 7, M = 2e5 + 7;

int h[N], e[M], w[M], ne[M], idx; //邻接表建图
int n, m, k;
int dist[N]; //最短距离
int sum[N]; // 最短路径树
int q[N];// 模拟队列
int p[N];//输入的路径
bool st[N]; //标记数组

void add(int a, int b, int c)  // 添加一条边a->b,边权为c
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

void bfs(int s)
{
	//初始化
    memset(dist, 0x3f, sizeof dist); 
    memset(st, 0, sizeof st);
    int hh = 0, tt = 1;
    q[0] = s; 
    dist[s] = 0;
    st[s] = 1; 
    while (hh != tt) {
        int t = q[hh++];
        if (hh == N) hh = 0;
        for (int i = h[t]; ~i; i = ne[i]) { //遍历当前节点连着的反向边
            int j = e[i];
            if (dist[j] > dist[t] + 1) { // 能更新最短路
                dist[j] = dist[t] + 1;
                sum[j] = 1; //方案数初始化
                if (!st[j]) { // 没走过, 放入队列
                    st[j] = 1;
                    q[tt++] = j;
                    if (tt == N) tt = 0;
                }
            }
            // 也是最短路,方案数 + 1
            else if (dist[j] == dist[t] + 1) sum[j] ++;
        }
    }
}

int main()
{
    memset(h, -1, sizeof h);//邻接表初始化
    scanf("%d%d", &n, &m);
    while (m--) {
        int a, b;
        scanf("%d%d", &a, &b);
        add(b, a, 1);//反向建图
    }
    scanf("%d", &k);
    //输入规定走法
    for (int i = 1; i <= k; ++i) scanf("%d", &p[i]);
    //cout << p[k] << endl;
    bfs(p[k]); //反向求最短路
    int mi = 0, mx = 0;
    for (int i = 1; i < k; ++i) {
        if (dist[p[i]] == dist[p[i + 1]] + 1) { //当前行走是在某一条最短路上
            if (sum[p[i]] > 1) mx++;//最短路路径数 > 1, 最大提示+1
        } // 不在最短路上, 最大提示 + 1, 最小提示 + 1
        else mi++, mx++;
    }
    cout << mi << ' ' << mx << endl;
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值