Codeforces Round 894 (Div. 3) A-F题解

A. Gift Carpet

题意

最近,特马和维卡庆祝了家庭日。他们的朋友 Arina 送给他们一块地毯,这块地毯可以用拉丁文小写字母的nm表来表示。

维卡还没看过礼物,但特马知道她喜欢什么样的地毯。如果维卡能在地毯上读出自己的名字,她一定会喜欢的。她从左到右逐列阅读,并从当前列中选择一个或零个字母。

从形式上看,如果可以从左到右依次选择四个不同的列,使第一列包含 "v",第二列包含 "i",第三列包含 "k",第四列包含 "a",那么女孩就会喜欢这块地毯。

帮助 Tema 提前了解 Vika 是否会喜欢 Arina 的礼物。

题解

将每列的字符找出,使用string保存,然后从左到右通过string.find()依次寻找vika

代码

#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
#define SZ(x) (int)(x.size())
#define FOR(i,x,y) for (int i = (x),_##i = (y);i < _##i;i++)
#define FORD(i,x,y) for (int i = (x),_##i = (y);i > _##i;i--)
inline void GIL();
int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);
    int t = 1;
    cin >> t;
    FOR (id, 1, t + 1) { GIL(); }
    return 0;
}
inline void GIL() {
    int n, m;
    cin >> n >> m;
    vector<string> v(m, "");	// 用来存储列字符串,一共m列。
    int ans = 0;
    string t = "vika";
    FOR (i,0, n) {	// i:[0, n - 1]
        string s;
        cin >> s;
        FOR (j, 0, m) {	// j:[0, m - 1]
            v[j] += s[j];	// 第j个字符放入第j列中。
        }
    }
    int idx = 0;
    FOR (i, 0, m) {	// i:[0, m - 1]
        if (v[i].find(t[idx]) != v[i].npos) idx++;
        if (idx == 4) break;	// 由于字符串'vika'的最大下标为3,当idx为4时就退出。
    }
    cout << (idx == 4 ? "YES\n" : "NO\n");	// 判断是否依次全部找到。
}

B. Sequence Game

题意

特马和维卡正在玩下面的游戏。

首先,维卡想出一个长度为 m的正整数序列 a,并把它写在一张纸上。然后,她又换了一张纸,按照下面的规则写下序列 b

  • 首先,她写下 a1
  • 然后,她只写下那些ai2im),使得ai1ai。这个序列的长度记为 n

例如,从序列a=[4,3,2,6,3,3],维卡将得到序列b=[4,6,3]

然后,她把写有序列b的纸片交给特马。他则尝试猜出序列 a

特马认为在这样的游戏中获胜的可能性很小,但他仍然希望至少找到一个序列 a可能是维卡最初选择的。请帮助他并输出任何这样的序列。

注意,您输出的序列长度不应超过输入序列长度的两倍。

题解

首先b的第一个元素是a中的,然后对b中的其他元素:

  • 如果bi1bi,那么将bi放入a中。

    当前a的最后一个元素一定是bi1,那么将bi放入a后,一定能从a中得到bi,因为bi1bi

    例如:b=[2,3,4]

    按照上面的规则,可以得到a=[2,3,4],而这个a是可以再推出b=[2,3,4]的。

  • 否则,即bi1>bi,将两个bi放入a中。

    两个bi放入a后,a的后三个元素依次为[bi1,bi,bi],由于bi1>bi,满足ai1ai的就只有[bi,bi],并得到一个bi

    例如:b=[3,2,1]

    按上面的规则,可以得到a=[3,2,2,1,1],同样也可以再推出b=[3,2,1]的。

按上面的规则得到的a最长为2×n1,小于2×n,满足要求。

代码

#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
#define SZ(x) (int)(x.size())
#define FOR(i,x,y) for (int i = (x),_##i = (y);i < _##i;i++)
#define FORD(i,x,y) for (int i = (x),_##i = (y);i > _##i;i--)
inline void GIL();
int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);
    int t = 1;
    cin >> t;
    FOR (id, 1, t + 1) { GIL(); }
    return 0;
}
inline void GIL() {
    int n;
    cin >> n;
    vector<int> v(n);	// 用来存放b数组
    FOR (i, 0, n) cin >> v[i];
    vector<int> ans;	// 用来存放a数组
    ans.push_back(v[0]);	// 首先b[0]一定在a中
    FOR (i, 1, n) {	// i:[1, n - 1]
        if (v[i] >= v[i - 1]) ans.push_back(v[i]);	// 只需要放一个
        else {	// 需要放两个
            ans.push_back(v[i]);
            ans.push_back(v[i]);
        }
    }
    cout << SZ(ans) << "\n";
    FOR (i, 0, SZ(ans)) {
        cout << ans[i] << " \n"[i == SZ(ans) - 1];
    }
}

C. Flower City Fence

题意

安雅住在花城。根据市长的命令,她必须为自己修建一道围墙。

围栏由 n块木板组成,每块木板高 ai米。根据命令,木板的高度不得增加。换句话说,对于所有的i<j,一定满足aiaj

安雅开始好奇她的栅栏是否与对角线对称。换句话说,如果她按照相同的顺序水平铺设所有木板,是否会得到相同的栅栏。

例如,对于n=5a=[5,4,3,2,1],栅栏是对称的。因为如果所有木板都横向铺设,栅栏将是[5,4,3,2,1],如图所示。

左边的栅栏是 [5,4,3,2,1],右边的栅栏同样是水平铺设的

但是对于n=3a=[4,2,1],栅栏并不对称。因为如果所有的木板都横向铺设,栅栏将是[3,2,1,1],如图所示。

左边的栅栏是 [4,2,1],右边的栅栏同样是水平放置的

帮助安雅判断她的栅栏是否对称。

题解

我们可以进行模拟。先得到一列一列摆放好的木板,再尝试在现在摆放好的木板上一行一行地再次覆盖原来的木板。以上面的两张图片为例,将右边的图片放到左边的图片上,看右边的图片是否能与左边的图片完全重合。如果能,就是对称的;否则就不是对称的。

那如何实现模拟上诉过程呢?

mii表示[1,i]ai的最小值,由于ai是非递增的,所以mi数组就是a数组。

我们尝试将右边的图片中的木板,从下往上,一个一个地平移到左边图片中。

那么对于右边的从下往上的第i个木板来说,其长度为ai,将其平移到左边图片后,如果想完全重合,那么左边图片在下标从1到下标ai的范围中,其最小值需要大于等于i。即需要满足a[ai]i

这里a[ai]i,里面的ai表示下标为ai,而a[ai]表示下标1到下标ai的最小值mi[ai]

所以要满足对称,那么ai就需要当a的下标,那么ai就需要小于等于n

代码

#include <iostream>
#include <vector>
using namespace std;
#define FOR(i,x,y) for (int i = (x),_##i = (y);i < _##i;i++)
#define FORD(i,x,y) for (int i = (x),_##i = (y);i > _##i;i--)
inline void GIL();
int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);
    int t = 1;
    cin >> t;
    FOR (id, 1, t + 1) { GIL(); }
    return 0;
}
inline void GIL() {
    int n;
    cin >> n;
    vector<int> v(n + 1, 0);	// a数组
    bool ans = 1;	// 记录答案,1表示对称,0表示不对称。
    FOR (i, 1, n + 1) {
        cin >> v[i];
        if (v[i] > n) {	// a[i] > n,一定不满足对称。
            ans = 0;
        }
    }
    if (!ans) {
        cout << "NO\n";
        return;
    }
    FOR (i, 1, n + 1) {
        if (v[v[i]] < i) { // 平移,需要满足a[a[i]] >= i
            ans = 0;
            break;
        }
    }
    cout << (ans ? "YES\n" : "NO\n");
}

D. Ice Cream Balls

题意

特马决定提高自己制作冰淇淋的技能。他已经学会了如何恰好用两个球把冰淇淋做成圆锥形。

在痴迷冰淇淋之前,特马对数学很感兴趣。因此,他很想知道,要制作恰好 n种不同冰淇淋,最少需要多少个球。

有很多可能的冰淇淋口味:1,2,3,.特马可以用任何口味(可能相同)制作双球冰淇淋。

如果组成冰淇淋的两个球的味道的集合是不同的,则这两个冰淇淋被认为是不同的。例如,{1,2}={2,1},但是{1,1}{1,2}

例如,有以下冰淇淋球 {1,1,2},特马只能做两种冰淇淋 {1,1}{1,2}

注意,特马不需要同时制作所有的甜筒冰淇淋。这意味着他可以独立制作冰淇淋甜筒。另外,为了在某个x的情况下制作出下面的甜筒{x,x},特马至少需要x类型的2个球。

请帮助特马回答这个问题。可以证明答案总是存在的。

题解

对于不同口味的x个球来说,可以构成Cx2种不同的冰淇淋。那么我们可以二分得到最小的x,使得Cx2n。我们想得到恰好n种冰淇淋的话,对于Cx12多的部分left=nCx12,我们可以添加与x个球味道相同的球,使得恰好能组成n种不同的冰淇淋。

代码

#include <algorithm>
#include <iostream>
using namespace std;
using ll = long long;
using i128 = __int128_t;
#define FOR(i,x,y) for (int i = (x),_##i = (y);i < _##i;i++)
#define FORD(i,x,y) for (int i = (x),_##i = (y);i > _##i;i--)
inline void GIL();
int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);
    int t = 1;
    cin >> t;
    FOR (id, 1, t + 1) { GIL(); }
    return 0;
}
inline void GIL() {
    ll n;
    cin >> n;
    ll l = 2, r = 1e18;
    auto check = [&] (ll mid) {
        i128 t = mid;	// 防爆longlong
        return (t * (t - 1) / 2 >= n);	// C_{t}^2 >= n
    };
    while (l < r) {	// 二分得到x,使得C_x^2 >= n
        ll mid = (l + r) >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    ll all = l * (l - 1) / 2;	// 计算C_X^2
    if (all > n) {
        all = n - (l - 1) * (l - 2) / 2;	// left = n - C_{x-1}^2
        l += all - 1;	// 这个-1是为了得到答案,按我的想法,应该是加all。
    }
    cout << l << "\n";
}

E. Kolya and Movie Theatre

题意

最近,科里亚发现他所在的城市即将开一家新的电影院,每天都会放映一部新电影,连续放映n天。因此,在数字1in的那一天,电影院将首映i部电影。此外,科里亚还找出了电影的排片表,并为每部电影分配了娱乐价值,用ai表示。

然而,Kolya 没有去电影院看电影的时间越长,下一部电影的娱乐价值的下降幅度就越大。这种下降相当于dcnt,其中d是一个预定值,cnt是距离上次去电影院的天数。我们还知道,科里亚设法在新电影院开业前一天去了另一家电影院,这一天的数字是0因此,如果我们在数字i的那一天第一次去电影院,那么cnt------等于距离上次去电影院的天数i

例如,如果d=2a=[3,2,5,4,6],那么通过观看指数为13的电影 1这一天的cnt值等于10=13这一天的cnt值等于31=2,所以电影的总娱乐值为a1d1+a3d2=321+522=2

不幸的是,科里亚只有时间去看最多 m部电影。帮助他制定一个参观电影院的计划,使他参观的所有电影的总娱乐价值最大化。

题解

我们可以发现,假设最后一部电影是在x天看的,那么不管前面看了多少部电影,值d对总娱乐值的贡献为x×d。也就是说,是否选择看电影对x×d无影响。

那么我们可以贪心,遍历天数,对于每一天i,都假设这是看电影的最后一天,在这i部电影中,最多选择m部电影,其娱乐值的和为sum,那么这时候的最大总娱乐值为sumi×d

那么,我们想让最大娱乐值最大,那么sum就需要最大,那么我们就需要娱乐值最大的m部电影,并且所有娱乐值都大于0。考虑使用最小堆来维护最大的m个娱乐值以及sum

代码

#include <algorithm>
#include <iostream>
#include <queue>
#include <utility>
#include <vector>
using namespace std;
using ll = long long;
#define FOR(i,x,y) for (int i = (x),_##i = (y);i < _##i;i++)
#define FORD(i,x,y) for (int i = (x),_##i = (y);i > _##i;i--)
inline void GIL();
int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);
    int t = 1;
    cin >> t;
    FOR (id, 1, t + 1) { GIL(); }
    return 0;
}
struct cmp {
    bool operator () (int &a, int & b) {
        return b < a;	// 用来确保最小值在堆顶部。
    }
};
inline void GIL() {
    int n, m;
    ll d;
    cin >> n >> m >> d;
    vector<ll> v(n);
    priority_queue<int, vector<int>, cmp> pq;	// 最小堆
    ll ans = 0, sum = 0;	// 最后的答案ans,还有sum
    FOR (i, 0, n) {	// i:[0,n-1]
        cin>> v[i];
        if (v[i] <= 0) continue;	// 忽略小于0的值
        if (pq.empty() || SZ(pq) < m) {	// 如果还不够m部电影,就一直看
            sum += v[i];
            pq.push(v[i]);
        } else if (SZ(pq) == m && v[i] > pq.top()) {	// 已经看了m部电影,那么可以去掉最小的值,放入较大的值
            sum += v[i] - pq.top();
            pq.pop();	// 去掉最小的
            pq.push(v[i]);	// 放入比较大的
        }
        ans = max(ans, sum - (i + 1) * d);	// 更新一下答案。
    }
    cout << ans << "\n";
}

F. Magic Will Save the World

题意

黑暗力量的传送门在两个世界的交界处打开了,现在整个世界都面临着可怕的威胁。为了关闭传送门,拯救世界,你需要打败一个又一个从传送门中出现的n怪物。

只有女魔法师维卡才能胜任。她拥有两种魔法能力--水魔法和火魔法。在一秒钟内,维卡可以产生 w个单位的水系法力和 f个单位的火系法力。她需要法力值来施法。最初维卡有0个单位的水系法力值和0个单位的火系法力值。

每个从传送门中出现的n个怪物都有自己的力量,用正整数表示。要打败强度为si的第i个怪物,维卡需要施放至少相同强度的水系法术或火系法术。换句话说,维卡可以在水系法术上花费至少 si个单位的水系法力,或者在火系法术上花费至少 si个单位的火系法力。

维卡可以瞬间创造和施放法术。只要有足够的法力,维卡每秒可以施放无限多个法术。

女巫想要尽快拯救世界,请告诉她需要多少时间。

题解

由于要求最小时间,表示可以二分找到这个最小时间。

那么,对于一个时间mid,我们如何确定是否能在mid秒内打败所有怪物呢?

首先,我们拥有两种法术,那么,对于一种法术而言,其值为val,我们想最大程度地使用这种法术,所以我们需要在n种怪物的数值中,找到和sum最接近val的值。而这个,可以通过0/1背包实现,背包总容量为val,每种怪物i相当于容量为si,价值为si的物品。求出此时的最大价值即为sum

然后再判断另一种法术是否能打败剩下的怪物即可。

代码

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
using ll = long long;
#define SZ(x) (int)(x.size())
#define FOR(i,x,y) for (int i = (x),_##i = (y);i < _##i;i++)
#define FORD(i,x,y) for (int i = (x),_##i = (y);i > _##i;i--)
inline void GIL();
int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);
    int t = 1;
    cin >> t;
    FOR (id, 1, t + 1) { GIL(); }
    return 0;
}
inline void GIL() {
    ll w, f;
    cin >> w >> f;
    int n;
    cin >> n;
    ll sum =0;
    vector<int> v(n);
    FOR (i, 0, n) {
        cin >> v[i];
        sum += v[i];	// 所有怪物的总和为sum
    }
    ll mx = max(w, f);
    ll l = 1, r = (sum + mx - 1) / mx;	// 二分边界,只使用一种法术消灭怪物时的最小时间。
    auto check = [&] (ll mid) {
        ll sum_w = mid * w, sum_f = mid * f;
        ll ALL = min(sum_w, sum_f);	// 采取最小的法术作为背包容量。
        vector<int> dp(ALL + 1, 0);	// 容量为ALL的背包
        FOR (i, 0, n) {
            FORD (j, ALL, v[i] - 1) {
                dp[j] = max(dp[j], dp[j - v[i]] + v[i]);	// 0/1背包
            }
        }
        return max(sum_f, sum_w) >= sum - dp[ALL];	// 看另一种法术是否能消灭剩下的怪物。
    };
    while (l < r) {	// 二分时间。
        ll mid = (l + r) >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    cout << l << "\n";
}
posted @   Uzhia  阅读(163)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示