收录范围#
CF 1931 Div.3 DCF 1931 Div.3 ECF 1931 Div.3 GCF 2008 Div.3 GCF 2008 Div.3 HCF 1925 Div.2 BCF 1928 Div.2 CCF 1928 Div.2 DCF 1928 Div.2 ECF 2004 EDU Div.2 DCF 2004 EDU Div.2 ECF 1957 Div.2 CCF 1957 Div.2 DCF 1957 Div.2 ECF 1935 Div.2 DCF 1997 EDU Div.2 D
CF 1931 Div.3 D - Divisible Pairs#
给出一个数组 ,求满足 的个数。
Solution#
易得:
存下 ,满足 即为对应的条件
void solve() {
int n, x, y;cin >> n >> x >> y;
vector<int> a(n);
for (auto& i : a)cin >> i;
ll cnt = 0;
map<pair<int, int>, int> mod;
for (int i = 0;i < n;i++) {
cnt += mod[{(x - a[i] % x) % x, a[i] % y}];
mod[{a[i] % x, a[i] % y}]++;
}
cout << cnt << '\n';
}cpp或
void solve() {
int n, x, y;cin >> n >> x >> y;
vector<int> a(n);
for (auto& i : a)cin >> i;
ll cnt = 0;
map<pair<int, int>, int> mod;
for (int i = 0;i < n;i++) {
mod[{a[i] % x, a[i] % y}]++;
}
for (int i = 0;i < n;i++) {
--mod[{a[i] % x, a[i] % y}];
cnt += mod[{(x - a[i] % x) % x, a[i] % y}];
++mod[{a[i] % x, a[i] % y}];
}
cout << cnt / 2 << '\n';
}cppCF 1931 Div.3 E - Anna and the Valentine’s Day Gift#
给出一个长度为 的数组 ,Anna 先手
- Anna 选择一个 ,去掉前导 0
- Sasha 选择
当 Anna 下完棋且数组中只剩下一个元素的时候,游戏结束,如果剩下的数字 ,Sasha 获胜,反之,Anna 获胜。
双方都以最佳方式玩游戏,输出获胜方。
Solution#
博弈论容易发现,Anna 每次优先翻转 后导 0 最多的数,Sasha 每次优先以 有最多后导 0 的数 作为 。
以 为例执行流程:
- Anna: Sasha:
- Anna: Sasha:
- Anna: Sasha:
- Anna: , Sasha WIN
即每次 Anna 删除一个最多的,Sasha 保留一个第二多的,以此类推。
比较简单。
void solve() {
int n, m;cin >> n >> m;
vector<pair<int, int>> a(n);for (auto& [i, j] : a)cin >> j;
int sum = 0;
for (int i = 0;i < n;i++) {
string num = to_string(a[i].second);
sum += num.size();
int cnt = 0;
for (int i = num.size() - 1;i >= 0;i--) {
if (num[i] == '0')cnt++;
else break;
}
a[i].first = cnt;
}
sort(a.begin(), a.end());
for (int i = n - 1;i >= 0;i -= 2) {
sum -= a[i].first;
}
if (sum - 1 >= m)cout << "Sasha\n";
else cout << "Anna\n";
}cppCF 1931 Div.3 G - One-Dimensional Puzzle#

每种类型包含 个元素。如果成功地将所有元素组合成一条长链,就算完成了。求有多少种方法(不能翻转)。
Solution#
组合数 (隔板法)Codeforces Round 925 (Div. 3) A-G 比赛录屏+讲解(60分钟开始)_哔哩哔哩_bilibili ↗
注意:本题写的 写为了 (写反了 )
前置小练习:
将 10 个相同的小球放入 8 个盒子里(每个盒子至少有一个小球),有多少种放法?
插空法:10 个小球有 9 个空可以插,有 7 个隔板, (即两块板之间不能相邻且第一块板左侧与最后一块板右侧必须有球)。
则答案
将 10 个相同的小球放入 8 个盒子里 (盒子可以为空),有多少种放法?
隔板法:10 个小球和 7 个隔板,可以组成一个由 17 个物件的排列,从中选择 7 个位置放置隔板,这样就可以把小球分配到 8 个盒子中
答案:
求 有多少个正整数解?
即在 10 个 1 之间的 9 个空里选择 3 个位置插入隔板,答案:
求 有多少个非负整数解?
法一:可以变换一下思路:这时的 ,将等式两边同时加上 4,则 求 的正整数解
法二:将 3 个隔板和 10 组成一个 13 件物品的排列,选择 3 个位置放置隔板
答案:
则可以抽象:
求 的非负整数解个数以及正整数解个数。
将 个相同的小球放入 个盒子里, (盒子可以为空和至少一个)各有多少种放法?
可以在 之间插入 ,在 之间插入
- 放在 和 之间
- 放在 和 之间
本题目插入的意思 ,设空位个数为
- 先考虑把同类元素( 个 3 或 个 4)放到 个空位中(可以为空)的方法个数
- 非负整数解个数
下面的个数为 ,由于插入 不冲突,所以直接相乘。
当
- 空位:
- 空位:
方案数:
当
- 空位:
方案数:
当
- 空位:
方案数:
然后就是预处理与逆元的计算。这里把阶乘、逆元阶乘预处理到上界之后,所有组合数都可以 取出;若不想写线性逆元,直接快速幂也能过,只是常数稍大一些。
const int mod = 998244353, N = 2e6 + 10;
ll fac[N], jv[N], inv[N];
void init(int n) {
jv[0] = fac[0] = 1;
for (int i = 1;i <= n;i++) {
inv[i] = i == 1 ? 1 : (mod - mod / i) * inv[mod % i] % mod;
fac[i] = fac[i - 1] * i % mod;
jv[i] = jv[i - 1] * inv[i] % mod;
}
}
ll C(int m, int n) {
if (n < m || m < 0) return 0;
return fac[n] * jv[n - m] % mod * jv[m] % mod;
}
void solve() {
int a, b, c, d;cin >> a >> b >> c >> d;
if (abs(a - b) >= 2) {
cout << 0 << '\n';return;
}
ll ans = 0;
if (!a && !b)ans = c == 0 || d == 0;
else if (a == b) {
ans = (C(a - 1, a + c - 1) * C(a, a + d) % mod + C(a, a + c) * C(a - 1, a + d - 1) % mod) % mod;
} else if (a == b - 1) {
ans = C(a, a + c) * C(a, a + d) % mod;
} else if (a == b + 1) {
ans = C(a - 1, a + c - 1) * C(a - 1, a + d - 1) % mod;
}
cout << ans << '\n';
}
main::init(2e6);cppCF 2008 Div.3 G - Sakurako’s Task#
樱子为你准备了一项任务:
她给了你一个由 整数组成的数组,让你选择 和 这样的 和 ,然后指定 或 。只要满足条件,您可以对任意 和 执行任意次数的操作。
樱子问你,数组的 在任意多次操作后的最大可能值是多少?
是数组中不存在的第 个非负整数。例如:,因为 是数组中不存在的第一个元素;,因为 是数组中不存在的第二个元素。
Solution#
- 需要证明: 根据操作,可以得到最终的的数组是
- 得到了这个数组,如何以一种较快的方式求出
最终能凑出来的是
在每个间隔有 个数没有凑出,一共有 个 凑不出来。
如果大于了这个数,说明已经超出了 ,在范围外面还有 个数, 即 ,否则同理
按照这种方式来求
#define int long long
void solve() {
int n, k;cin >> n >> k;
vector<int> a(n + 1);
for (int i = 1;i <= n;i++)cin >> a[i];
int g = a[1];
for (int i = 1;i <= n;i++)g = __gcd(g, a[i]);
if (n == 1) {
cout << (a[1] >= k ? k - 1 : k) << '\n';return;
}
int t = (g - 1) * (n - 1);
if (k > t) {
cout << n - 1 + k << '\n';
} else {
if (k % (g - 1) == 0) {
cout << k / (g - 1) * g - 1 << '\n';
} else {
cout << k / (g - 1) * g + k % (g - 1) << '\n';
}
}
}cppCF 2008 Div.3 H - Sakurako’s Test#
樱子即将参加一项测试。测试可以描述为一个整数数组 和一个任务:
给定一个整数 ,樱子可以执行以下操作任意多次:
- 选择一个整数 ( ),使得 ;
- 将 的值改为 。
任意多次使用此操作,她必须找出数组 的最小中值 。
樱子知道数组,但不知道整数 。有人说漏了嘴, 的 值中有一个会出现在下一次测试中,所以樱子问你每个这样的 的答案是什么。
长度为 的数组的中位数是位于排序数组中间的元素(对于偶数 ,位于 -th 位置;对于奇数,位于 -th 位置)。
Solution#
调和级数复杂度<->mod Q
根据贪心的思想,
提前预处理出 的所有情况,对于每一种情况,答案范围是 。
可以二分答案(对于 ,数组 的最小中位数),对于每一个 ,可以计算出余数为 区间内的数的个数,只需要判断个数是否占 个即可。
时间复杂度:
void solve() {
int n, q;cin >> n >> q;
vector<int> mp(n + 1);
for (int i = 1;i <= n;i++) {
int x;cin >> x;mp[x]++;
}
for (int i = 1;i <= n;i++)mp[i] += mp[i - 1];
vector<int> ans(n + 1);
for (int i = 1;i <= n;i++) {//x
int l = 0, r = i - 1;
while (l < r) {
int mid = l + r >> 1;
int s = 0;
for (int j = 0;j <= n;j += i) {
s += mp[min(n, j + mid)] - (j == 0 ? 0 : mp[j - 1]);
}
if (s >= n / 2 + 1) {
r = mid;
} else {
l = mid + 1;
}
}
ans[i] = l;
}
while (q--) {
int x;cin >> x;
cout << ans[x] << ' ';
}
cout << '\n';
}cppCF 1925 Div.2 B - A Balanced Problemset?#
将 分解为 个整数组成,使得这 个数的 gcd 最大,也就是最平衡。
Solution#
想了半天没想出来
没太搞懂
这段代码已经被 HACK 了,现在提交会 T 
void solve()
{
int n, x;
cin >> x >> n;
if (n == 1)
{
cout << x << '\n';
return;
}
for (int i = x / n; i >= 1; i--)
{
if (x % i == 0 && x / i >= n)
{
cout << i << '\n';
return;
}
}
}cpp有:
所以答案一定是 的一个约数。
现在,考虑 的一个因数 ,我有两种想法
- 当 ,选择为: 有可行答案
- 当 ,有可行答案 (本来每一份是 份,这时 ,如果分为 份每一份就是 可以分为更多份,每一份也更大, 的部分可以直接安到任意位置,这时的 也可以作为答案)
- 直接遍历因子 到 ,然后判断 并取 ,会超时
- 只遍历因子 到 ,再分别检查 和 是否满足条件,这样就能把所有因子都覆盖到。
找到满足该条件的最大 。这可以在 时间内通过简单的因数分解来实现。

void solve()
{
int n, x;
cin >> x >> n;
int ans = 1;
for (int i = 1; i * i <= x; i++)
{
if (x % i == 0)//如果i是x的因子
{
if (n <= x / i)//n*i<=x ->i i i ... x-(n-1)*i ->i
ans = max(ans, i);
if (n <= i)
ans = max(ans, x / i);
}
}
cout << ans << '\n';
}cppCF 1928 Div.2 C - Physical Education Lesson#
每个人都排成一排,并被要求站在 “第 个 “的位置上。
以此类推。这样,每隔 个位置就重复一次。( )
给出在队伍中的位置 结算时得到的数字 ,输出有多少个符合条件的 。
eg , = 。
解决这些问题的例子是 :
| / № | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
Solution#
- 当在左边部分的时候:只需要满足 即可(当为 0 时需要特判为 2)
- 反之满足 即可
暴力做法:(TLE)由于是 ()的做法肯定会 T
void solve() {
int n, x;cin >> n >> x;
int ans = 0;
for (int k = 2;k <= n;k++) {
int m = n % (2 * k - 2);
if (!m) m = 2;
if (m <= k) {
if (m == x) ans++;
} else {
m = 2 * k - m;
if (m == x) ans++;
}
}
cout << ans << '\n';
}cpp由上文: 满足
可以变形为:
所以只需要判断哪些数是 的偶数 (由于(2k-2)一定是偶数) 因子,即代表 ,所以存入的时候存 即 。
与 还满足
NOTE: 试除法:要找到某个数的所有因子只需要 在 是因子的情况下加入 即:
(不过当 为平方数的时候会有重叠,将 vector 换为 set 去重即可)
auto find = [&](int n) {
vector<int> ans;
for (int i = 1;i * i <= n;i++) {
if (n % i == 0) {
ans.push_back(i);
ans.push_back(n / i);
}
}
return ans;
};cppunordered_set 可换为 set。但是 unordered_set 的插入是 的
void solve() {
int n, x;cin >> n >> x;
unordered_set<int> can;
auto find = [&](int a) {
unordered_set<int> ans;
for (int i = 1;i * i <= a;i++) {
if (a % i == 0) {
if (i % 2 == 0)can.insert(1 + i / 2);
if ((a / i) % 2 == 0)can.insert(1 + (a / i) / 2);
}
}
return ans;
};
for (auto i : find(n - x))can.insert(i);
for (auto i : find(n + x - 2))can.insert(i);
int ans = 0;
for (auto i : can) {
if (i >= x)ans++;
}
cout << ans << '\n';
}cppCF 1928 Div.2 D - Lonely Mountain Dungeons#
中土世界居民的军队将由几个小队组成。众所周知,每一对同种族的生物分属不同的小队,军队的总兵力就会增加 个单位。但由于提摩西很难领导一支由大量小队组成的军队,因此由 个小队组成的军队的总兵力会减少 个单位。注意,军队总是由个至少一个班组成。
有 个种族,而 个种族的生物数量等于 。确定他们所能组建的军队的最大兵力。
Solution#
三分(先单增,再单减)组合数
注意:long long 的计算稍不注意就会wa
三分这里的关键不是模板本身,而是先证明 check(k) 呈现“先增后减”的单峰形态。因为随着队伍数增加,每个种族内部贡献的变化是平滑的,总答案可以按单峰函数处理,于是可以安全三分,再在收缩后的短区间里暴力枚举收尾。
codeforces round 924 (Div2) A-E 思路分享_哔哩哔哩_bilibili ↗
对于计算分成 个队伍时的战斗力(每个种族其实是独立的,所以可以分开计算):
先算出 的整数部分,意味着每个队伍都至少有 个人
当每个队伍都是 个人,这时的贡献:
对于一个人来说,其他队伍的人都有助于贡献:
对于和他同一个队伍的人来说:
其他队伍的也是一样,所以这时的贡献:
余数部分,只有部分队伍仅有 1 人,贡献:
余数和整数部分的接壤:则贡献:
对于后加入的余数部分,之前整数部分和他不在同一个队伍的 个人需要算上贡献
则该种族的战斗力:
void solve() {
int n, b, x;cin >> n >> b >> x;
vector<int> c(n);for (int i = 0;i < n;i++)cin >> c[i];
auto check = [&](ll k) {//计算分成k个队伍时的战斗力
ll ans = 0;
for (auto i : c) {
ll t = i / k, tt = i % k, res = 0;
res += t * t * k * (k - 1) / 2;
if (tt > 0) {//可忽略这个条件
res += tt * (tt - 1) / 2;
res += tt * t * (k - 1);
}
ans += res * b;
}
ans -= (k - 1) * x;
return ans;
};
ll l = 1, r = *(max_element(c.begin(), c.end()));
while (r - l + 1 > 3) {//这段代码的主要目的是将l,r区间缩小到足够小了方便之后枚举
ll lmid = l + (r - l + 1) / 3;
ll rmid = r - (r - l + 1) / 3;
if (check(lmid) > check(rmid))r = rmid;//目的为了缩减区间长度
else l = lmid;
}
ll ans = 0;
for (ll i = l;i <= r;i++)ans = max(ans, check(i));
cout << ans << '\n';
}cppD_哔哩哔哩_bilibili ↗ (我认为最简单的思路):在 中会有一些重复的数字,如果依次枚举的话重复的数字就重复计算了,所以可以算出之后乘以个数即可。
复杂度:最坏情况 (有 个种族人数不同): 互不相同 ,
注:
num数组只能开在外面,开在里面会 T ,要将num数组放里面,需要将num的大小改为
vector<int> num(2e5 + 1);
void solve() {
int n, b, x;cin >> n >> b >> x;
unordered_set<int> c;
for (int i = 0;i < n;i++) {
int x;cin >> x;
c.insert(x);
num[x]++;
}
auto check = [&](ll k) {
ll ans = 0;
for (auto i : c) {
ll t = i / k, tt = i % k, res = 0;
res += t * t * k * (k - 1) / 2;
res += tt * (tt - 1) / 2;
res += tt * t * (k - 1);
ans += res * b * num[i];
}
ans -= (k - 1) * x;
return ans;
};
ll l = 1, r = *(max_element(c.begin(), c.end())), ans = 0;
for (ll i = l;i <= r;i++)
ans = max(ans, check(i));
cout << ans << '\n';
for (auto p : c)num[p] = 0;
}cppCodeforces Round 924 (Div. 2) A - E - 知乎 ↗
可以看出,最佳方案肯定是将每个种族均匀分配到每个队伍中。
假设某个种族有 个,我们可以暴力枚举队伍数为 时对答案的贡献,对于 区间贡献是确定的,可以使用静态区间加实现。
由于 有限制,所以这种方法可以在 的时间内完成。
void solve() {
int n, b, x, m = 0;cin >> n >> b >> x;
vector<int>c(n);for (int i = 0;i < n;i++)cin >> c[i], m += c[i];
vector<ll>d(m + 1);
auto add = [&](int l, int r, ll x) {
d[l] += x;
if (r + 1 <= m)d[r + 1] -= x;
};
for (auto x : c) {
for (int i = 1;i < x;i++) {
ll sum = 1ll * x * (x - 1);
int t = x / i, r = x % i;
sum -= (i - r) * 1ll * t * (t - 1);
sum -= r * 1ll * (t + 1) * t;
add(i, i, sum / 2);
}
add(x, m, 1ll * x * (x - 1) / 2);
}
ll ans = 0;
for (int i = 1;i <= m;i++) {
d[i] += d[i - 1];
ans = max(ans, d[i] * b - 1ll * (i - 1) * x);
}
cout << ans << '\n';
}cppD_哔哩哔哩_bilibili ↗ 平均分配最优,先排序,枚举队伍数量,种族数量小的下次不算了,数量大的在算一遍,直到不会被更新
#define int long long
void solve() {
int n, b, x;cin >> n >> b >> x;
vector<int> a(n);for (auto& i : a)cin >> i;sort(a.begin(), a.end());
int maxx = 1e9, divi = 0, hascal = 0;
int ans = 0;
for (int i = 2;divi <= n - 1 && i <= maxx;i++) {
int res = 0;
for (int j = divi;j <= n - 1;j++) {
if (a[j] <= i) {
hascal += b * (a[j] * (a[j] - 1)) / 2;
divi++;
} else {
int cnt = a[j] / i, mod = a[j] % i;
int a1 = i - mod, a2 = mod;
res += b * a[j] * (a[j] - 1) / 2;
res -= b * a1 * cnt * (cnt - 1) / 2;
res -= b * a2 * (cnt + 1) * cnt / 2;
}
}
res += hascal;
res -= (i - 1) * x;
ans = max(ans, res);
}
cout << ans << '\n';
}cppCF 1928 Div.2 E - Modular Sequence#
给你两个整数 和 。长度为 的序列 如果是 ,且对于所有 的 值要么是 要么是
判断是否存在长度为 的模数序列,其元素之和等于 ,如果存在,请找出任何这样的序列。
Solution#
DP暴力搜索
void solve() {
int n, x, y, s;cin >> n >> x >> y >> s;vector<int> a(n, 0);a[0] = x;
ll sum = x;
auto dfs = [&](auto self, int i, ll currSum) -> bool {
if (currSum > s) return false; // 前缀和剪枝
if (i == n) return currSum == s;
a[i] = (a[i - 1] + y) % y;
if (self(self, i + 1, currSum + a[i])) return true;
a[i] = a[i - 1] + y;
if (self(self, i + 1, currSum + a[i])) return true;
return false;
};
if (dfs(dfs, 1, sum)) {
cout << "yes\n";for (int i = 0;i < n;i++)cout << a[i] << " \n"[i == n - 1];
} else {
cout << "no\n";
}
}cpp官方题解:
我们先来看答案的形式:首先会有形如 的前缀,然后会有一些形如 的块。
我们可以从序列的所有元素中减去 ,然后将所有元素除以 (所有元素最初的余数都为 ,因此它们都是可被 整除的)。设 。那么我们的序列将以 开头,然后会有形如 的块。
现在让我们计算这些值: 表示形如 且和为 的块序列的最小长度。我们可以利用动态规划计算从 到 的所有数的这个值。如果我们已经处理了从 到 的所有值,那么对于 ,我们已经计算出了最小长度,并且我们可以更新 的 值,总共不超过 的值有 个。在同一个 中,我们可以记录经过哪些值进行了重新计算,以便恢复答案。
现在,我们可以遍历形如 的第一个块的长度。然后我们就知道剩余块的和,并利用预先计算的 ,我们可以确定是否能够形成所需的序列。
codeforces round 924 (Div2) A-E 思路分享_哔哩哔哩_bilibili ↗
序列形式如下图(当全部都减去 ,并除以 )
第一部分
其余部分
dp[x]={达到这个面积最少的长度,从哪里转移过来,最后高度}
要满足条件必须满足:dp[s][0]<=n
这里继续往下推,其实就是一个背包式 DP:dp[s] 维护凑出面积 s 时所需的最小长度、转移来源和最后一层高度。只要最后 dp[s][0] <= n,就说明能在长度限制内构造成功,再按记录的前驱回溯方案即可。

void solve() {
ll n, x, y, s;cin >> n >> x >> y >> s;
if (x + (n - 1) * (x % y) > s) {
cout << "NO\n";return;
}
s -= (x % y) * n;
vector<array<int, 3>>dp(s + 1, {INT_MAX,0,0});
for (int ns = x - x % y, h = x - x % y, i = 1;ns <= s;i++, h += y, ns += h) {
dp[ns] = {i,0,h};
}
for (int i = 0;i <= s;i++) {
if (dp[i][0] != INT_MAX) {
for (int j = 1, t = 0/*t代表一部分的柱子的长度*/;t + i <= s;t += j * y, j++) {
dp[t + i] = min(dp[t + i], {dp[i][0] + j, i, (j - 1)* y});
}
}
}
if (dp[s][0] > n) {
cout << "NO\n";return;
}
vector<int> ans(n);
int v = s;
while (v) {
auto [i, ns, h] = dp[v];
while (v != ns) {
i--;
ans[i] = h;
v -= h, h -= y;
}
}
cout << "YES\n";
for (auto i : ans)cout << i + (x % y) << " ";cout << "\n";
}cppCF 2004 EDU Div.2 D - Colored Portals#
在一条直线上有 座城市。这些城市的编号从 到 。
传送门用于在城市之间移动。传送门有 种颜色:蓝色、绿色、红色和黄色。每个城市都有两种不同颜色的传送门。如果城市 和城市 的传送门颜色相同,则可以从城市 移动到城市 (例如,可以在 “蓝-红 “城市和 “蓝-绿 “城市之间移动)。这种移动需要花费 枚金币。
你的任务是回答 个独立的问题:计算从城市 移动到城市 的最小花费。
Solution#
二分
表面看是最短路,其实仔细想想就知道不是这个做法
思路:
从题目可以知道,一共六种组合,若出现两个城市无法配对的问题,则最多只用中转一次就能到另外一个城市
若除了 颜色没有其他颜色的城市了,则无法到达,否则一定能找到一个城市作为中转达到另外一个城市。
现在就有另外一个问题:如何在其他不同颜色的所有城市中找到使得 最小的值呢?
如果能找到一个 使得 ,则这时答案就是 .
若不能找到这样的 ,则每次二分的去寻找使得答案相对最小的,即距离 或 最近的 即可,在这里面取最小值即可。
思路完全正确,但是写代码写错了… 正确代码如下:
const string tar[] = {"BG", "BR", "BY", "GR", "GY", "RY"};
void solve() {
int n, q;cin >> n >> q;
vector<string> s(n + 1);
vector<vector<int>> idx(6);
for (int i = 0;i < 6;i++)idx[i].push_back(0);
for (int i = 1;i <= n;i++) {
cin >> s[i];
for (int j = 0;j < 6;j++)if (s[i] == tar[j])idx[j].push_back(i);
}
for (int i = 0;i < 6;i++)idx[i].push_back(1e9);
while (q--) {
int x, y;cin >> x >> y;
if (x > y)swap(x, y);
if (s[x][0] == s[y][0] || s[x][0] == s[y][1] || s[x][1] == s[y][1] || s[x][1] == s[y][0]) {
cout << y - x << '\n';continue;
}
int ok = 0;
for (int i = 0;i < 6;i++) {
if (tar[i] == s[x] || tar[i] == s[y] || !idx[i].size()) continue;
auto k = lower_bound(idx[i].begin(), idx[i].end(), x);
if (*k != 1e9 && *k < y) {
ok = 1;break;
}
}
if (ok) {
cout << y - x << '\n';continue;
}
int ans = 1e9;
for (int i = 0;i < 6;i++) {
if (tar[i] == s[x] || tar[i] == s[y] || !idx[i].size()) continue;
auto k1 = lower_bound(idx[i].begin(), idx[i].end(), x);
if (*k1 != 1e9) {
ans = min(ans, *k1 - y + *k1 - x);
}
auto k2 = upper_bound(idx[i].begin(), idx[i].end(), x);
--k2;
if (*k2 != 0) {
ans = min(ans, x + y - *k2 - *k2);
}
}
if (ans == 1e9)ans = -1;
cout << ans << '\n';
}
}cpp其实代码还可以简化:
const string tar[] = {"BG", "BR", "BY", "GR", "GY", "RY"};
void solve() {
int n, q;cin >> n >> q;
vector<string> s(n + 1);
vector<vector<int>> idx(6);
for (int i = 0;i < 6;i++)idx[i].push_back(0);
for (int i = 1;i <= n;i++) {
cin >> s[i];
for (int j = 0;j < 6;j++)if (s[i] == tar[j])idx[j].push_back(i);
}
for (int i = 0;i < 6;i++)idx[i].push_back(1e9);
while (q--) {
int x, y;cin >> x >> y;
if (x > y)swap(x, y);
if (s[x][0] == s[y][0] || s[x][0] == s[y][1] || s[x][1] == s[y][1] || s[x][1] == s[y][0]) {
cout << y - x << '\n';continue;
}
int ans = 1e9;
for (int i = 0;i < 6;i++) {
if (tar[i] == s[x] || tar[i] == s[y] || !idx[i].size()) continue;
auto k = lower_bound(idx[i].begin(), idx[i].end(), x);
if (*k != 1e9) {
ans = min(ans, abs(*k - y) + *k - x);
}
--k;
if (*k != 0) {
ans = min(ans, x + y - *k - *k);
}
}
if (ans == 1e9)ans = -1;
cout << ans << '\n';
}
}cppCF 2004 EDU Div.2 E - Not a Nim Problem#
爱丽丝和鲍勃正在玩一个游戏。他们有 堆棋子,其中 堆最初有 颗棋子。
在他们的回合中,玩家可以选择任意一堆石子,并从中取出任意正数的石子,但有一个条件:
- 让当前石堆中的石子数量为 。从棋子堆中取走 ,使得 和 的最大公约数等于 1。
无法下棋的棋手输棋。两位棋手都以最佳状态下棋(也就是说,如果一位棋手的策略能让他获胜,那么无论对手如何回应,他都会获胜)。爱丽丝先下。
确定谁会赢。
Solution#
博弈论 /SG 函数
EDU:学习博弈论
可以先暴力列出 函数,根据 sg 函数的值来找规律:
void solve() {
constexpr int N = 500;
vector<int> sg(N);
for (int i = 1;i < N;i++) {
set<int>s;
for (int j = 1;j <= i;j++) {
if (__gcd(i, j) == 1) {
s.insert(sg[i - j]);
}
}
int idx = 0;
for (auto x : s) {
if (x == idx)idx++;
}
sg[i] = idx;
}
vector<vector<int>> q(N);
for (int i = 0;i < N;i++)q[sg[i]].push_back(i);
for (int i = 0;i < N;i++) {
cout << i << ": ";
for (auto x : q[i])cout << x << " ";
cout << '\n';
}
}cpp得到 :
0 1 0 2 0 3 0 4 0 2 0 5 0 6 0 2 0 7 0 8 0 2 0 9 0 3 0 2 0 10 0 11 0 2 0 3 0 12 0 2 0 13 0 14 0 2 0 15 0 4 0 2 0 16 0 3 0 2 0 17 0 18 0 2 0 3 0 19 0 2 0 20 0 21 0 2 0 4 0 22 0 2 0 23 0 3 0 2 0 24 0 4 0 2 0 3 0 25 0 2 cpp: (即 )
0: 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60 62 64 66 68 70 72 74 76 78 80 82 84 86 88 90 92 94 96 98
1: 1
2: 3 9 15 21 27 33 39 45 51 57 63 69 75 81 87 93 99
3: 5 25 35 55 65 85 95
4: 7 49 77 91
5: 11
6: 13
7: 17
8: 19
9: 23
10: 29
11: 31
12: 37
13: 41
14: 43
15: 47
16: 53
17: 59
18: 61
19: 67
20: 71
21: 73
22: 79
23: 83
24: 89
25: 97 cpp易得
- 当 为偶数时
- 只看第一项,从 2 开始,之后的每一项是 都是质数项即
- 在一个值 对应多个 时,可以发现后面的数的最小质因数就是第一个质数,即: ,
则有:,
constexpr int N = 1e7;
int sg[N + 1];
void solve() {
int n;cin >> n;
int ans = 0;
for (int i = 1;i <= n;i++) {
int x;cin >> x;ans ^= sg[x];
}
if (ans != 0) {
cout << "Alice\n";
} else {
cout << "Bob\n";
}
}
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
sieve(N);
sg[0] = 0;sg[1] = 1;sg[2] = 0;
for (int i = 1;i < primes.size();i++) {
sg[primes[i]] = i + 1;
}
for (int i = 3;i <= N;i++) {
sg[i] = sg[minp[i]];
}
int _ = 1;
cin >> _;
while (_--)
solve();
}cppCF 1957 Div.2 C - How Does the Rook Move?#
给您一个 棋盘,您和电脑轮流在棋盘上分别放置白车和黑车。在放置车的过程中,您必须确保没有两只车互相攻击。如果两只车共用同一行或同一列,则无论颜色如何,都会互相攻击。
有效的一步棋是将一只车放在一个位置( , )上,使它不会攻击任何其他车。
你先开始,当你在自己的回合中走了一步有效的棋,将白车置于位置( , )时,电脑会照搬你的棋,在它的回合中将黑车置于位置( , )。如果是 ,那么电脑就无法映射你的棋步,并跳过它的回合。
您已经与计算机下了 步棋(计算机也会尝试复制这些棋步),您必须继续下棋直到没有剩余的有效棋步为止。在下完 步之后,如果继续下棋,最终会有多少种不同的配置?可以保证 步和隐含的计算机步都是有效的。由于答案可能较大,请打印出 的模数。
如果存在一个坐标( , ),其中一个配置中有一个车,而另一个配置中没有,或者坐标上的车的颜色不同,那么这两个配置就被认为是不同的。
Solution#
组合数学/DP法一:DP 转移方程:f[i] = f[i - 1] + f[i - 2] * 2 * (i - 1)
若从后往前推:
对于棋下在对角线上时,选择 这时消耗了 则贡献为
若没有下在对角线上:选定该行 后,则列可以在前面 个位置任意挑选,列 同理,消耗 ,则贡献为
#define int long long
constexpr int mod = 1e9 + 7;
int f[300010];
void solve() {
int n, k;cin >> n >> k;int cnt = n;
for (int i = 1;i <= k;i++) {
int x, y;cin >> x >> y;cnt -= 2 - (x == y);
}
cout << f[cnt] << '\n';
}
signed main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
f[0] = 1;f[1] = 1;f[2] = 3;
for (int i = 3;i <= 3e5;i++) {
f[i] = f[i - 1] + f[i - 2] * 2 * (i - 1);f[i] %= mod;
}
int _ = 1;
cin >> _;
while (_--)
solve();
}cpp组合数学:
代表可用的行列数,则:
不懂是如何推出的
#define int long long
constexpr int mod = 1e9 + 7;
int fac[300010];
int inv(int a, int b = mod - 2) {
int ans = 1;
while (b) {
if (b & 1)ans = (ans * a) % mod;
a = (a * a) % mod;b >>= 1;
}
return ans;
}
int c(int a, int b) {
return fac[a] * inv(fac[a - b]) % mod * inv(fac[b]) % mod;
}
void solve() {
int n, k;cin >> n >> k;int cnt = n;
for (int i = 1;i <= k;i++) {
int x, y;cin >> x >> y;cnt -= 2 - (x == y);
}
int ans = 0;
for (int i = 0;i * 2 <= cnt;i++) {
ans = (ans + c(cnt, 2 * i) * c(2 * i, i) % mod * fac[i]) % mod;
}
cout << ans << '\n';
}
signed main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
fac[0] = fac[1] = 1;
for (int i = 2;i <= 3e5;i++) {
fac[i] = fac[i - 1] * i % mod;
}
int _ = 1;
cin >> _;
while (_--)
solve();
}cppCF 1957 Div.2 D - A BIT of an Inequality#
给你一个数组 。请找出有多少个图元( )符合下列条件:
- , 和
- .
定义 。
Solution#
位运算
设前缀异或为 , 代表前 个数进行异或。
则可以将要求简化为求 的个数。
现在进行分类讨论:设 当前比特位分别为 , 代表的是 最高比特位的值
当 时:无论 为 , 的值都不会变
当 时,若此时 的值不同,则最终的值会变小,否则最终的值会变大
总结:变大的情况只有 时才会出现。
这样对于 ,只要先找到其最高位为 1 的位置
由题目可得 ,所以答案就是 两边都是 1 的情况加上两边都是 的情况。
即 中 1 的数量乘以 中 1 的数量加上 中 0 的数量乘以 中 0 的数量
1 的数量算出来之后,这个区间 0 的数量就是区间长度减去 1 的数量。
#define int long long
int c[32][100010];
void solve() {
int n;cin >> n;vector<int> a(n + 1), s(n + 1);
for (int i = 1;i <= n;i++) {
cin >> a[i];s[i] = s[i - 1] ^ a[i];
for (int j = 0;j <= 30;j++) {
c[j][i] = c[j][i - 1] + (s[i] >> j & 1);
}
}
int ans = 0;
for (int i = 1;i <= n;i++) {
int k = __lg(a[i]);
ans += (c[k][n] - c[k][i - 1]) * c[k][i - 1];
ans += (n - i + 1 - (c[k][n] - c[k][i - 1])) * (i - c[k][i - 1]);
}
cout << ans << '\n';
}cpp现在只需要进行预处理:需要得到的是 ,即:从 到 的元素的第 位一起异或起来是 还是 1
这里需要处理其前缀与后缀:(官方题解)
pre[i][j][k = 0]代表 个数的第 位是 的个数, 同理。
suf[i][j][k = 0]代表 个数的第 位是 的个数, 同理。注意:由于这里没有错位的进行前缀与后缀计算,所以在对 进行操作时,需要将 归于
constexpr int Z = 30, MAX_N = 1e5 + 3;
int pref[Z][MAX_N][2];
int suff[Z][MAX_N][2];
void solve() {
int n;
cin >> n;
vector<int> a(n + 1);
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 0; i < Z; i++) suff[i][n + 1][0] = suff[i][n + 1][1] = 0;
for (int i = 0; i < Z; i++) {
for (int j = 1; j <= n; j++) {
int t = (a[j] >> i) & 1;
for (int k = 0; k < 2; k++) pref[i][j][k] = (t == k) + pref[i][j - 1][k ^ t];
}
for (int j = n; j >= 1; j--) {
int t = (a[j] >> i) & 1;
for (int k = 0; k < 2; k++) suff[i][j][k] = (t == k) + suff[i][j + 1][k ^ t];
}
}
long long ans = 0;
for (int i = 1; i <= n; i++) {
int z = __lg(a[i]); // <-> 31 - __builtin_clz(a[i])
ans += 1ll * pref[z][i - 1][1] * (1 + suff[z][i + 1][0]);
ans += 1ll * (1 + pref[z][i - 1][0]) * suff[z][i + 1][1];
}
cout << ans << "\n";
}cppCF 1957 Div.2 E - Carousel of Combinations#
给你一个整数 。函数 表示从集合 { } 中选择 个不同的数字并将它们围成一个圆圈 的不同方法的数目。
求
由于这个值可能非常大,因此求它的模数 。
Solution#
威尔逊定理容易知道
可以知道的是 有 项,由于是连续的 个数,每个数对 的 模数一定不一样 ,其中一定有一项能够被 整除(),至于其他项则构成了
定理:对于素数 ,,对于合数 ,
这里利用差分数组 ,对于位置 ,则要使得 的结果相同, 即 位置的贡献 和 位置相同,直到 才使得 增加了 1,所以在 位置加上对应贡献,在 位置减去相应贡献,这样,对 数组进行前缀和后就能够计算出每个位置正确的贡献,对答案而言,是 的贡献之和,所以还需要对 数组再进行一次前缀和则就是对应的答案。
#define int long long
constexpr int N = 1e6, mod = 1e9 + 7;
int vis[N + 10], pri[N + 10], isp[N + 10];//判断是否是素数
int d[N + 10];
void init(int n) {//线性筛
int cnt = 0;
for (int i = 2; i <= n; ++i) {
if (!vis[i]) {
pri[cnt++] = i;isp[i] = 1;
}
for (int j = 0; j < cnt; ++j) {
if (i * pri[j] > n) break;
vis[i * pri[j]] = 1;
if (i % pri[j] == 0) break;
}
}
}
signed main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
init(N);
for (int j = 2;j <= N;j++) {
if (j == 4 || isp[j]) {
int v = (j == 4 ? 2 : j - 1);
for (int i = j;i <= N;i += j) {//枚举j的倍数
int t = v * (i / j) % j;
d[i] = (d[i] + t) % mod;
if (i + j <= N)d[i + j] = (d[i + j] - t + mod) % mod;
}
}
}
for (int i = 1;i <= N;i++)d[i] = (d[i] + d[i - 1]) % mod;
for (int i = 1;i <= N;i++)d[i] = (d[i] + d[i - 1]) % mod;
int _;cin >> _;
while (_--) {
int n;cin >> n;cout << d[n] << '\n';
}
}cppCF 1935 Div.2 D - Exam in MAC#
给出一个长度为 的数组 ,求 (满足 都不是数组中的元素, )的对数
Solution#
容斥原理/组合数学主要是要往这个方面想,想到了就很简单了。
官方题解:
用容斥原理:。
的总对数数量是 。
。遍历和值 ,假设 ,则对于 会有一个对应的 ,因此具有这样的和的对的数量是 。
。遍历差值 ,假设 ,则对于 会有一个对应的 ,因此具有这样的差的对的数量是 。
。假设 ,,则 ,。当奇偶性相同时就可以计入答案。 让我们计算 中偶数和奇数的数量——分别为 和 。因此,这样的对的数量是 。
#define int long long
void solve() {
int n, c;cin >> n >> c;
vector<int> a(n);
for (int i = 0;i < n;i++) {
cin >> a[i];
}
int cnt = (c + 2) * (c + 1) / 2, odd = 0;
for (int i = 0;i < n;i++) {
cnt -= a[i] / 2 + 1;
cnt -= c - a[i] + 1;
if (a[i] % 2)odd++;
}
int even = n - odd;
cnt += (odd + 1) * odd / 2 + (even + 1) * even / 2;
cout << cnt << '\n';
}cppCF 1997 EDU Div.2 D - Maximize the Root#
给你一棵有根的树,由 个顶点组成。树中的顶点编号为 至 ,根顶点为 。数值 写在第 个顶点上。
您可以执行以下任意次数(可能为零)的操作:选择一个顶点 该顶点至少有一个子顶点。对 的子树( 本身除外)中的所有顶点 ,将 增加 ,并将 减少 。但是,每次操作后,所有顶点上的值都应该是非负值。
您的任务是通过上述操作计算出写入根的最大可能值。
Solution#
树形 DP/二分
这个题目一眼二分,但是可惜自己的图论方面的代码能力太差,没写出来。(似乎有许多解法)
dfs 判断是否能够在 的子树中得到至少 的值
dfs 思路是:对于 1 的子树而言,需要满足大于等于 mid 的需求,若不满足,则子树的节点需要被增加一些才能满足需求,而这里增加了对于该子树节点的子树会提出更高的要求,即 会增加。
#define int long long
void solve() {
int n;cin >> n;
vector<int> a(n + 1);
for (int i = 1;i <= n;i++)cin >> a[i];
vector<vector<int>> g(n + 1);
for (int i = 2;i <= n;i++) {
int x;cin >> x;
g[x].push_back(i);
}
auto dfs = [&](auto self, int u, int x)->bool {
if (x > 1e9)return 0;
bool leaf = 1;
if (u != 1) {
x += max(0ll, x - a[u]);
}
for (auto v : g[u]) {
leaf = 0;
if (!self(self, v, x))return 0;
}
return (!leaf || x <= a[u]);
};
int l = 0, r = 1e10;
while (l < r) {
int mid = l + r >> 1;
if (!dfs(dfs, 1, mid))r = mid;
else l = mid + 1;
}
cout << a[1] + l - 1 << '\n';
}cpp
思路和我刚开始的想法接近,尽量让上下两层最小值接近,
若 子树的最小值 比 还小,则证明子树没什么能提交给 的了,这个子树最多能凑出 的值,
若 子树的最小值 比 大,则可以按照题目要求将子树中的值整体提交给父亲一些,当接近的时候最优
搜索之后,根节点的子树则为下面的节点提交后的值,则答案就是 加上子树的最小值。
#define int long long
void solve() {
int n;cin >> n;
vector<int> a(n + 1), val(n + 1);
for (int i = 1;i <= n;i++)cin >> a[i];
vector<vector<int>> g(n + 1);
for (int i = 2;i <= n;i++) {
int x;cin >> x;
g[x].push_back(i);
}
int inf = 1e18;
auto dfs = [&](auto self, int u)->void {
val[u] = a[u];
int mi = inf;
for (auto v : g[u]) {
self(self, v);mi = min(mi, val[v]);//mn是求的u节点子树的最小值
}
if (mi != inf) {// 不是叶子节点
if (val[u] > mi) {
val[u] = mi;
} else {
val[u] = val[u] + mi >> 1;
}
}
};
dfs(dfs, 1);
int ans = inf;
for (auto v : g[1]) {
ans = min(ans, val[v]);
}
cout << ans + a[1] << '\n';
}cpp