AtCoder Beginner Contest 356

A - Subsegment Reverse (abc356 A)

题目大意

给定一个 1,2,3,...,n的排列a,给定两个数 l,r,左右颠倒a[l..r]。输出。

解题思路

按照题意模拟即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, a, b;
cin >> n >> a >> b;
--a;
vector<int> ans(n);
iota(ans.begin(), ans.end(), 1);
reverse(ans.begin() + a, ans.begin() + b);
for (auto x : ans)
cout << x << ' ';
cout << '\n';
return 0;
}


B - Nutrients (abc356 B)

题目大意

给定一天n种营养的摄入目标量。

给定m种食物的n种营养的含量。

问是否所有营养都达到目标摄入了。

解题思路

按照题意,对每种营养的摄入总量求和,与目标比较即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, m;
cin >> n >> m;
vector<int> a(m);
for (auto& x : a)
cin >> x;
while (n--) {
for (auto& x : a) {
int s;
cin >> s;
x -= s;
}
}
bool ok = true;
for (auto x : a) {
ok &= x <= 0;
}
if (ok) {
cout << "Yes" << '\n';
} else {
cout << "No" << '\n';
}
return 0;
}


C - Keys (abc356 C)

题目大意

n把钥匙,有些真的,有些假的。一个门,可以拿一些钥匙打开它,若其中有 k个真的钥匙,则门打开。

给定了 m条记录,表示用了哪些钥匙,门是否打开。

对于这n把钥匙的真假,共 2n种情况,问有多少种情况,不违反上述的记录。

解题思路

n只有 15,直接 O(2n)枚举所有情况,然后枚举每个记录,检测其钥匙能否打开门,与结果是否相符。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, m, k;
cin >> n >> m >> k;
vector<pair<vector<int>, int>> a(m);
for (auto& [v, c] : a) {
int x;
cin >> x;
v.resize(x);
for (auto& x : v) {
cin >> x;
--x;
}
string s;
cin >> s;
c = s[0] == 'o';
}
int ans = 0;
int up = (1 << n);
for (int i = 0; i < up; ++i) {
bool ok = true;
for (auto& [v, c] : a) {
int cnt = 0;
for (auto x : v) {
if (i & (1 << x)) {
++cnt;
}
}
if ((cnt >= k) ^ c) {
ok = false;
break;
}
}
ans += ok;
}
cout << ans << '\n';
return 0;
}


D - Masked Popcount (abc356 D)

题目大意

给定n,m,求 k=0npopcount(k&m)

popcount(x)表示 x二进制下 1的个数。

解题思路

n高达 1018,直接枚举会超时。

考虑贡献转换。

考虑到答案来自于二进制下1的个数。 由于&运算的特性,这些实际都是来自于m的每一个1。 我们需要考虑m二进制下每一个 1对答案的贡献,即有多少个 k,使得 k&m后该位是 1

假设m的二进制表示为 1...10...0,考虑第 i位上的1,思考有多少个 k,使得 k&m的第i位是 1

考虑如何计算 k的数量,首先, k的第 i位一定是 1,然后就剩下低位高位的情况数。低位就是低于i位的那些位数的取值,高于 i位的就是高于 i位的位数取值。这个计数问题其实和数位dp差不多。

由于k有最大值 n的限制,低位的情况数会依赖于高位 ,为方便表述,设高位是up,当前位是 middle,低位是 down,比如 n=110101,当前第 i=2位(从 0开始),则 up=110,middle=1,down=01。然后情况数其实就分两种:

  • 如果高位取值和n高位不一致,则低位取值没有限制,因此低位的情况数是2i ,而高位的情况数是up,若此时 m的第 i位是 1,则其贡献(出现的次数)为 2i×up
  • 如果高位取值和n高位一致,则当前位低位的都会收到n的限制。如果此时 middle=0,则说明该位不能取 1,则 m的第 i位没有贡献。否则 middle=1低位的情况数就有 down+1种情况,而高位的 情况数只有1种,若此时 m的第 i位是 1,则其贡献(出现的次数)为 down+1

综上,考虑第i位,其中 n的高位是 up,当前位是 middle,低位是 down,若 m的第 i位是 1,则其对答案的贡献(满足 k&m的第 i位是 1k的个数)为 2i×up+middle×(down+1)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int mo = 998244353;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
LL n, m;
cin >> n >> m;
LL ans = 0;
LL up = n >> 1, middle = n & 1, down = 0, cnt = 1;
for (int i = 0; i < 60; ++i) {
if ((m >> i) & 1) {
ans += 1ll * up * cnt % mo + middle * (down + 1);
ans %= mo;
}
down = down | (middle << i);
middle = up & 1;
up >>= 1;
cnt <<= 1;
}
cout << ans << '\n';
return 0;
}


E - Max/Min (abc356 E)

题目大意

给定一个数组a,求 i=1nj=i+1nmax(ai,aj)min(ai,aj)

解题思路

作除法始终是最大值除以最小值,因此可以先对a进行排序再计算,这不会影响答案。

a从小到大排序后,考虑枚举 j,然后计算 i<j情况对答案的贡献,即枚举最大值。

一个比较明显的观察是,可能会有若干个 ai,使得 ajai是同样的值。那我们可以把这些值合并地来算,即×个数

事实上可以通过数论分块来求解,ajai的值的可能数量和因子个数同一个数量级,即O(ai)个,而造成该取整结果的ai的取值就是数论分块里的两个边界 l,r,可以在 a中二分得到对应位置,因而得到个数。而这复杂度是O(nlognai),会超时。

数论分块复杂度是根号级别,在这里用不了,只能另寻它路。

这次考虑枚举 i,然后计算 i<j情况对答案的贡献,即枚举最小值。

同样考虑贡献转换,原本的想法是求ajai=valaj数量cnt,然后累计val×cnt

我们将其val看成1+1+1...,然后重组一下,变成求 ajaivalaj数量cnt

即原本是求ajai=1,2,3aj数量,现在求ajai1,2,3aj数量。

这里的贡献转换就是将ajai=3对答案有3的贡献,拆成了 1+1+1,即ajai1+ajai2+ajai3,然后合并所有的ajai1(或2,3),求其个数。

现在我们的视角转换成求 ajaivaj数量,即枚举v,求ajvaij的数量。很明显通过在a数组二分vai,就能求得j的数量了。

由于maxa=106,那么这里枚举的v的数量就是O(i=1nmaxaai)。每次枚举都有一个二分的O(logn),因此总的时间复杂度是O(i=1nmaxaailogn)

如果所有的ai都很小,比如都是 ai=1,那时间复杂度就是 O(nmaxalogn),又炸了!

但看这个式子,非常像对数求和,如果ai唯一,那复杂度就是O(i=1maxamaxailogn)=O(maxalogmaxalogn),是可过的。

因此,如果ai有重复的,那么我们就合并重复的数,并记录cntk表示 ai=k的数量,然后考虑怎么修正一下答案的计算。

合并相同的数,并从小到大排序,然后枚举当前的 ai,再枚举 v,求得第一个aj>vaiaj,那么此时ajaiv的数量就是cntai×kjcntak。而后者k=jncntaj就是一个关于cnt数组的后缀和,预处理一下就能O(1)得到。

然后对于相同数之间的贡献,则是icntai×(cntai1)2

最终的答案就是两者的和。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
vector<int> r(n);
for (auto& x : r)
cin >> x;
map<int, int> s;
for (auto& i : r)
s[i]++;
vector<int> a;
vector<int> sum;
for (auto& [x, y] : s) {
a.push_back(x);
sum.push_back(y);
}
vector<int> suf(sum.size());
partial_sum(sum.rbegin(), sum.rend(), suf.rbegin(), plus<int>());
LL ans = 0;
n = a.size();
for (int i = 0; i < n; ++i) {
int x = a[i];
int pos = i + 1;
ans += 1ll * sum[i] * (sum[i] - 1) / 2;
while (pos < n) {
pos = lower_bound(a.begin() + pos, a.end(), x) - a.begin();
if (pos < n)
ans += 1ll * sum[i] * suf[pos];
x += a[i];
}
}
cout << ans << '\n';
return 0;
}


F - Distance Component Size Query (abc356 F)

题目大意

<++>

解题思路

<++>

神奇的代码


G - Freestyle (abc356 G)

题目大意

<++>

解题思路

<++>

神奇的代码


posted @   ~Lanly~  阅读(850)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示