牛客竞赛_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ (nowcoder.com)
注:本题解只涉及赛时思路和赛后补题收获,人菜佬勿喷
赛时六题【ABCDEG】补题【FHIK】
J是个状压dp,没搞懂,暂时未补......
A.柠檬可乐
A-柠檬可乐_2024牛客寒假算法基础集训营4 (nowcoder.com)
一天,阿宁在街道看到有柠檬可乐卖,于是点了一杯。柠檬切片,倒可乐,摇晃一下,阿宁对于这么简单的步骤感到惊讶......
阿宁买的这杯柠檬可乐的甜度是a,酸度是 b 。如果 a≥k×b,那么阿宁就觉得好喝,否则阿宁就觉得亏了。
1≤a,b,k≤100
分析:
签到题,判断 a 与 b×k 的大小即可
代码:
void solve(){
int a, b, k;
cin >> a >> b >> k;
if(a >= k * b)
cout << "good";
else
cout << "bad";
}
B.左右互博
B-左右互博_2024牛客寒假算法基础集训营4 (nowcoder.com)
分析:
每堆石子最多可以操作 −1 次,所以只需要统计
−1 的总和,判断奇偶性即可
代码:
void solve(){
int n;
cin >> n;
vector<int>a(n);
int sum = 0;
for(int i = 0; i < n; i++)
cin >> a[i], sum += a[i] - 1;
if(sum % 2 == 0)
cout << "sweet";
else
cout << "gui";
}
C.冬眠
C-冬眠_2024牛客寒假算法基础集训营4 (nowcoder.com)
分析:
这个题的数据范围都很小,直接暴力模拟即可
代码:
void solve(){
int n, m, x, y;
cin >> n >> m >> x >> y;
char s[105][105];
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
cin >> s[i][j];
char a = s[x][y];
int p, q;
cin >> p >> q;
int op[105], z[105];
for(int i = 1; i <= q; i++){
cin >> op[i] >> z[i];
}
for(int k = 0; k < p; k++){
for(int j = 1; j <= q; j++){
if(op[j] == 1){
char r = s[z[j]][m], t;
for(int i = 1; i < m; i++){
t = s[z[j]][i];
s[z[j]][i] = r;
r = t;
}
s[z[j]][m] = r;
}
else{
char r = s[n][z[j]], t;
for(int i = 1; i < n; i++){
t = s[i][z[j]];
s[i][z[j]] = r;
r = t;
}
s[n][z[j]] = r;
}
}
}
cout << s[x][y];
}
D.守恒
D-守恒_2024牛客寒假算法基础集训营4 (nowcoder.com)
分析:
题目名字很关键,守恒。读题可以发现数组的总和是定值,所以只需要统计总和的因数,且这个因数大于n即可,感觉这道题跟cf前几天的一道题很像,那道题是筛素因子,这道题只需筛大于n的因数即可
代码:
(最后一个for循环的i开的int类型 结束条件写的 i∗n<=sum 结果爆掉了 超时了三发,哭死
void solve(){
ll n;
cin >> n;
vector<ll>a(n);
ll sum = 0;
for(ll i = 0; i < n; i++){
cin >> a[i];
sum += a[i];
}
if(n == 1){
cout << 1;
return;
}
ll ans = 0;
for(ll i = 1; i <= sum / n; i++){
if(sum % i == 0 && sum / i >= n)
ans++;
}
cout << ans;
}
E.漂亮数组
E-漂亮数组_2024牛客寒假算法基础集训营4 (nowcoder.com)
分析:
读完题感觉好像是dp,但是有将近1000发通过,恐怖如斯。
仔细一想,这个题只需要对区间和 %k 的值进行记录判断即可
如果两个区间和 %k 的值一样,那说明这两个区间相减即是漂亮数组( %k 的值相减之后变成0了)
如果一个区间和本身 %k 就是0,那说明这个区间也是个漂亮数组,直接统计即可
注意数据范围,记得开 longlong
代码:
void solve(){
ll n, k;
cin >> n >> k;
vector<ll>a(n + 1);
map<ll, ll> mp;
ll ans = 0;
ll sum = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
sum += a[i];
mp[sum % k]++;
if(sum % k == 0 || mp[sum % k] >= 2){
ans++;
mp.clear();
sum = 0;
}
}
cout << ans << endl;
}
G.数三角形(easy)
G-数三角形(easy)_2024牛客寒假算法基础集训营4 (nowcoder.com)
分析:
数据范围不是很大,用 dfs搜索即可,从"*"出发,假设当前坐标为dx, dy,那么(dx + 1,dy - 1)和(dx + 1, dy + 1)一定也是 "*",同时(dx + 1, dy)也应该是“*”,此时是高为1的情况,高为其他情况同理,使用 dfs 递归求解即可,但是太暴力的写法也会tle,所以提前求一个前缀和数组,用来记录每一行的“*”个数,这样在判断底边是否符合题意是便可以以O(1)复杂度查询。
代码:
void solve(){
int n, m;
cin >> n >> m;
char a[505][505];
int b[505][505];
for(int i = n; i >= 1; i--){
string s;
cin >> s;
for(int j = 1; j <= m; j++)
a[i][j] = s[j - 1];
}
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
b[i][j] = b[i][j - 1] + (a[i][j] == '*' ? 1 : 0);//前缀和数组
int ans = 0;
auto dfs = [&](auto self, int fx, int op, int po) -> void{
if(fx <= 0 || op <= 0 || po > m)
return;
if(a[fx][op] != '*' || a[fx][po] != '*')
return;
if(b[fx][po] - b[fx][op - 1] == po - op + 1)
ans++;
self(self, fx - 1, op - 1, po + 1);
};
for(int i = 2; i <= n; i++){
for(int j = 2; j <= m - 1; j++){
if(a[i][j] == '*'){
dfs(dfs, i - 1, j - 1, j + 1);
}
}
}
cout << ans;
}
F.来点每日一题
F-来点每日一题_2024牛客寒假算法基础集训营4 (nowcoder.com)
分析:
读完题发现很浓的dp味道,分析得分式子:
((b1−b2)×b3−b4)×b5−b6
要想值越大,那么必须前面的式子要越大,对于乘法运算来说,会出现两种情况
正数 × 正数 = 正数
负数 × 负数 = 正数
所以我们只需维护区间的最大最小值即可
定义函数maxn[i][j]代表区间从第i个物品到第j个物品之间取东西的最大价值。f[i][j]意味着取到i(1~6)个数字,即体积为i时,j = 0是最大价值,j = 1是最小价值
再定义一个 dp[i] 表示在前 i 个数,以第i个数为结尾并且这个区间选满了6个数的最大分数
最后 dp[n] 即为答案
代码:
ll dp[N], a[N], f[N][2], maxn[N][N], INF = 1e18;
void solve(){
ll n;
cin >> n;
for(int i = 1; i <= n ;i++)
cin >> a[i];
if(n < 6){
cout << "0" << endl;
return;
}
for(int i = 1; i <= n ;i++){
for(int j = 1; j <= 6; j++){
f[j][0] = -INF;
f[j][1] = INF;
}
for(int j = i; j <= n; j++){
for(int k = 6; k >= 1; k--){
if(k == 1){
f[k][0] = max(f[k][0], f[k - 1][0] + a[j]);
f[k][1] = min(f[k][1], f[k - 1][1] + a[j]);
}
else if(k % 2 == 0){
f[k][0] = max(f[k][0], f[k - 1][0] - a[j]);
f[k][1] = min(f[k][1], f[k - 1][1] - a[j]);
}
else{
for(int z = 0; z < 2; z++){
if(abs(f[k - 1][z]) > INF / 2)
continue;
f[k][0] = max(f[k][0], f[k - 1][z] * a[j]);
f[k][1] = min(f[k][1], f[k - 1][z] * a[j]);
}
}
}
maxn[i][j] = max(maxn[i][j], f[6][0]);
}
}
for(int i = 1; i <= n; i++){
for(int j = i - 1; j >= 0; j--){
dp[i] = max(dp[i], dp[j] + maxn[j + 1][i]);
}
}
cout << dp[n] << endl;
}
H.数三角形(hard)
H-数三角形(hard)_2024牛客寒假算法基础集训营4 (nowcoder.com)
分析:
与 G 题不同的是,这题的数据范围大了许多,不能再用暴力求解,基于 G题前缀和优化的思路,不难想到用树状数组进行优化,用树状数组来记录每个点下方的贡献,在统计前进行预处理即可
代码:
template <class T>
struct szarray{
int n;
vector<T> a;
szarray(int n = 0) : n(n), a(n, T()) {}
void modify(int i, T x) {
for (i++; i <= n; i += i & -i) {
a[i - 1] += x;
}
}
T get(int i) {
T res = T();
for (; i > 0; i -= i & -i) {
res += a[i - 1];
}
return res;
}
T sum(int l, int r) {
return get(r) - get(l);
}
};
void solve(){
int n, m;
cin >> n >> m;
vector<string>a(n);
vector l(n + 1, vector<int>(m + 2)), r(n + 1, vector<int>(m + 2));
for (int i = 0; i < n; i++) {
cin >> a[i];
for (int j = 0; j < m; j++) {
if (a[i][j] == '*') {
l[i + 1][j + 1] = l[i][j] + 1;
r[i + 1][j + 1] = r[i][j + 2] + 1;
}
}
}
vector z(2, szarray<int>(m));
ll ans = 0;
for (int i = 1; i < n; i++) {
vector<int> num(m + 1);
for (int j = m - 1; j >= 0; j--) {
if (a[i][j] == '*') {
num[j] = num[j + 1] + 1;
}
}
vector<vector<int>> sc(m);
for (int j = 0; j < m; j++) {
if (a[i][j] == '*') {
ans += z[j % 2].sum(j - l[i + 1][j + 1] * 2, m);
z[j % 2].modify(j, +1);
sc[min(j + num[j] - 1, j + r[i + 1][j + 1] * 2 - 2)].push_back(j);
for (auto k : sc[j]) {
z[k % 2].modify(k, -1);
}
}
}
}
cout << ans << endl;
}
I.回头
I-回头_2024牛客寒假算法基础集训营4 (nowcoder.com)
分析:
这个题是个很明显的最短路问题,跑一下dij,再记录一下是否被修改,得到最短路径即可
代码:
void solve(){
int n, m;
cin >> n >> m;
vector<vector<pair<int, int>>> adj(n);
vector<array<ll, 2>> f(n, {INF, INF});
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
u--, v--;
adj[u].emplace_back(v, w);
if (w < f[u][0]) {
f[u][1] = f[u][0];
f[u][0] = w;
} else if (w < f[u][1]) {
f[u][1] = w;
}
}
vector<array<ll, 2>> dis(n, {INF, INF});
priority_queue<tuple<ll, int, int>, vector<tuple<ll, int, int>>, greater<>>q;
q.emplace(0LL, 0, 0);
ll ans = INF;
while (!q.empty()) {
auto [d, x, t] = q.top();
q.pop();
if (dis[x][t] != INF){
continue;
}
dis[x][t] = d;
if (adj[x].size() > t){
for (auto [y, w] : adj[x]){
q.emplace(d + w + (t ? f[x][w == f[x][0]] : 0), y, 0);
q.emplace(d + (t ? f[x][w == f[x][0]] : 0), y, 1);
}
}
}
for (int i = 0; i < 2 && i <= adj[n - 1].size(); i++){
ans = min(ans, dis[n - 1][i] + (i == 0 ? 0 : f[n - 1][0]));
}
if (ans == INF) {
ans = -1;
}
cout << ans << endl;
}
K.方块掉落
K-方块掉落_2024牛客寒假算法基础集训营4 (nowcoder.com)
最近阿宁对一个名叫“方块掉落”的游戏感兴趣,沉迷于此。
每局游戏一开始,有一条无限长的水平线、一个箭头、一个操作序列 t ,没有任何方块。
箭头位水平线的下方,代表方块掉落的位置。
操作序列 t 是一个字符串,仅包含 "YBR" 三种字符,分别代表颜色黄蓝红。依次按照操作序列 s 掉落不同颜色的方块。
如果即将掉落的是黄色方块,那么箭头指向的位置掉落一个黄色方块。例如:
如果即将掉落的是蓝色方块,首先箭头移到下一个位置;然后箭头指向的位置掉落一个蓝色方块。例如:
如果即将掉落的是红色方块,在箭头指向的那一列,掉落这一列所有存在的方块;然后箭头指向的位置掉落一个红色方块。例如:
分析:
嗯,单点修改,区间查询,线段树无疑了
分析题目发现,我们只需要用ls与rs维护一段区域中, 最左端区间与最右端区间的方块高度,用lz记录最左与最右端的红色方块数
在 pushup 函数中,根据当前的颜色采取不同的修改即可
代码:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using db = double;
#define endl "\n"
#define rep(i, a, n) for(int i = a; i < n; i++)
#define per(i, a, n) for(int i = a; i >= n; i--)
#define all(x) x.begin(), x.end()
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
const double PI = acos(-1);
struct cmp{
template <typename T, typename U>
bool operator()(T const &left, U const &right)
{
return left.first > right.first;
}
};
const int Y = 'Y';
const int R = 'R';
const int B = 'B';
struct segtree{
ll l, r;
ll lz;
ll ls, rs;
ll res;
ll ok;
segtree(){
ls = rs = 0;
l = r = 0;
lz = 0;
res = 0;
ok = 0;
}
}tr[N << 2];
ll a[N];
void pushup(segtree &u, segtree &x, segtree &y){
u.ok = 1;
u.lz = 0;
if (!x.ok)
{
u.res = y.res;
u.rs = y.rs;
u.ls = y.ls;
u.lz = y.lz;
}
else if (!y.ok)
{
u.res = x.res;
u.rs = x.rs;
u.ls = x.ls;
u.lz = x.lz;
}
else
{
u.res = (x.res + y.res) % mod;
u.ls = x.ls;
u.rs = y.rs;
if (x.ls == x.res) u.ls += y.ls, u.ls % mod;
if (y.rs == y.res and y.ls) u.rs += x.rs, u.rs %= mod;
if (x.lz) u.lz += x.lz;
if (y.lz)
{
u.res = (y.res + x.res + x.rs * y.lz) % mod;
if (x.ls == x.res) u.lz += y.lz + (y.lz) * x.lz, u.lz %= mod, u.ls += x.res * y.lz, u.ls %= mod;
if (y.rs == y.res) u.rs += x.rs * y.lz, u.rs %= mod;
}
}
}
void pushup(ll u){
pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}
void build(ll u, ll l, ll r){
tr[u].l = l;
tr[u].r = r;
if(l == r){
tr[u].ok = 1;
tr[u].res = 1;
if(a[l] == Y){
tr[u].ls = 1;
tr[u].rs = 1;
}
if(a[l] == B){
tr[u].ls = 0;
tr[u].rs = 1;
}
if(a[l] == R){
tr[u].lz = 1;
tr[u].ls = 1;
tr[u].rs = 1;
}
return;
}
ll mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
pushup(u);
}
void modify(ll u, ll l, ll r, ll x){
if(tr[u].l >= l && tr[u].r <= r){
if(x == Y){
tr[u].lz = 0;
tr[u].ls = 1;
tr[u].rs = 1;
}
if(x == B){
tr[u].lz = 0;
tr[u].ls = 0;
tr[u].rs = 1;
}
if(x == R){
tr[u].lz = 1;
tr[u].ls = 1;
tr[u].rs = 1;
}
return;
}
if (tr[u << 1].r >= l)
modify(u << 1, l, r, x);
if (tr[u << 1 | 1].l <= r)
modify(u << 1 | 1, l, r, x);
pushup(u);
}
segtree query(ll u, ll l, ll r){
if(tr[u].l >= l && tr[u].r <= r){
return tr[u];
}
segtree res, tmp1, tmp2;
if (tr[u << 1].r >= l) tmp1 = query(u << 1, l, r);
if (tr[u << 1 | 1].l <= r) tmp2 = query(u << 1 | 1, l, r);
pushup(res, tmp1, tmp2);
return res;
}
void solve(){
ll n, q;
cin >> n >> q;
string s;
cin >> s;
for(int i = 1; i <= n; i++){
a[i] = s[i - 1];
}
build(1, 1, n);
while(q--){
ll op;
cin >> op;
if(op == 1){
ll p;
char ce;
cin >> p >> ce;
ll k = ce;
modify(1, p, p, k);
}
else{
ll ii, ll;
cin >> ii >> ll;
cout << query(1, ii, ll).res << endl;
}
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int _;
_ = 1;
//cin >> _;
while (_--) {
solve();
}
return 0;
}