整理的算法模板合集: ACM模板

点我看算法全家桶系列!!!

实际上是一个全新的精炼模板整合计划


目录

    • 【每日亿题】Codeforces Round #694 (Div. 1 + Div2)(A ~ H,8题全,超高质量题解)2021/2/1、2/2
  • Div 2 A、B、C、D、E、F
    • A - Strange Partition
    • B - Strange List
  • Div 1 A、B、C、D、E、F
    • C - Strange Birthday Party
    • D - Strange Definition
    • E - Strange Shuffle
    • F - Strange Housing
    • G - Strange Permutation
    • H - Strange Covering
  • 后记

【每日亿题】Codeforces Round #694 (Div. 1 + Div2)(A ~ H,8题全,超高质量题解)2021/2/1、2/2

每日亿题
2/1
这套 Div. 2 好简单呀,我写了不到两个小时都快 AK 了…
题解写了一个多小时,主要是 A 的题太多了,下次少 A 一点(bushi
2/2 更新
顺便写了一下Div1的多出来的那两道题,Div确实难度上升很多,不过写起来还是很爽的 ~

Div 2 A、B、C、D、E、F

比赛地址:https://codeforces.com/contest/1471

这场的Div2确实简单,不过E题是一道挺简单的题,过的人却很少,可能大家都没做过交互题吧 ~

A - Strange Partition

Problem A Strange Partition

给你一个数组 aaa 和一个整数 xxx,你可以无限地使用一个操作(也可以不用):将数列中两个相邻的数字加起来,数列长度-1。我们定义数列的价值为∑i=1k⌈aix⌉\sum_{i=1}^k \left\lceil \frac{a_i}{x} \right\rceil∑i=1k​⌈xai​​⌉。求我们利用这个操作可以得到的数列的最小价值和最大价值。

1≤ai≤109.1≤n≤105,1≤x≤109,1≤t≤10001 \leq a_i \leq 10^9.1 \leq n \leq 10^5,1 \leq x \leq 10^9,1 \le t \le 10001≤ai​≤109.1≤n≤105,1≤x≤109,1≤t≤1000

Solution

我们考虑本道题的唯一的操作,加起来,会对答案造成什么影响。

因为我们对于最后的价值来说,是除以 xxx 上取整求和,也就是对于一个数来说,如果是 xxx 的倍数,那么除以 xxx 上取整没有任何加成,但是如果不是 xxx 的倍数,那么除以 xxx 上取整总价值就会 + 1,但是如果把两个数加起来,可能原本能 + 2 的,加起来之后,就只能 + 1 了,所以我们通过分析性质,发现了本题的解题思路 :最小值就是把所有数加起来除以 xxx 上取整,因为本来有贡献的数(除以 xxx 上取整会多 + 1 ),每一个都能 + 1 ,但是所有的数加到一块以后,最多也就只能有一次 + 1 的贡献。最大值就是不改变任何数字。

detail

使用 ceil 函数的时候要先强转为 double ,因为整数的除法默认下取整。

Code

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cmath>
#include <unordered_map>
#include <bitset>using namespace std;
typedef long long ll;
//#define int __int128
const int N = 50007, M = 5000007, INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;int n;
ll m, t, x;int main()
{scanf("%d", &t);while(t -- ) {scanf("%d%lld", &n, &x);ll maxx = 0, minn = 0;for(int i = 1; i <= n; ++ i) {scanf("%lld", &m);minn += m;maxx += ceil((double)m / x);}minn = ceil((double)minn / x);printf("%lld %lld\n", minn, maxx);}
}

B - Strange List

Problem B Strange List

给你一个数组和整数 xxx ,一个指针,会遍历整个数列,对于当前的数组 qqq ,如果 qqq 能被 xxx 整除,则在数列的末尾加上 xxx 份 qx\cfrac{q}{x}xq​,当指针遇见不能被 xxx 整除的时候就退出,指针就不会再动了。求最后的总价值。价值定义为最后整个数列的值的和。

1≤ai≤109,1≤t≤100,2≤x≤109,1≤n≤1051 \leq a_i \leq 10^9,1 \leq t \leq 100,2 \leq x \leq 10^9,1 \leq n \leq 10^51≤ai​≤109,1≤t≤100,2≤x≤109,1≤n≤105。

Solution

首先第一步还是分析性质。

我们发现 qqq 能被 xxx 整除,那么在数列的末尾加上 xxx 份 qx\cfrac{q}{x}xq​,对答案的贡献实际上就是 x×qx=qx\times \cfrac{q}{x}=qx×xq​=q。这是解题的关键。我们发现不论是第一次还是第 nnn 次,对答案的贡献都是 qqq,这样我们就不用暴力地按照题意模拟求总价值,而是直接加上 qqq 即可。我们发现任何一个数,哪怕本来能被 xxx 整除,但是把除数放到最后末尾,指针指向的时候,总有一次,会被除尽,也就是指针暂停,末尾不会再增加数字,可以开始计算最终的答案了。也就是一旦除出来的数不能被 xxx 整除了,就 break 退出了。因为我们要每次加上 qqq ,所以我们不能改变原数组,而我们需要一直除,来判断是否能继续被 xxx 整除,所以我们可以把数组 aaa copy 到数组 bbb。

Code

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;
const int N = 1e5 + 7, mod = 1e9 + 7;
typedef long long ll;
int n, m, t, x;
int a[N], b[N];
ll ans;
ll sum;int main()
{scanf("%d", &t);while(t -- ) {ll ans = 0;scanf("%d%d", &n, &x);for(int i = 1; i <= n; ++ i) {scanf("%d", &a[i]);b[i] = a[i];ans += a[i];}bool flag = 0;while(true) {for(int i = 1; i <= n; ++ i) {if(b[i] % x) {flag = 1;break;}ans += a[i], b[i] /= x;}if(flag) break;}printf("%lld\n", ans);}return 0;
}

Div 1 A、B、C、D、E、F

比赛地址:https://codeforces.com/contest/1470

这题也太简单了吧,Div1写四道不是有手就行?

C - Strange Birthday Party

Problem C Strange Birthday Party

小鹿马上就要生日了!让我们祝她生日快乐!因为马上就要生日了,小鹿准备开一个生日派对,邀请了 nnn 位朋友,并且给第 iii 位朋友分配了一个编号 kik_iki​,现在小鹿要给她们每人一个礼物, 商店里有价格 从小到大 的 mmm 件各不相同的商品,每一件商品都只有一件,每件商品只能买一次,其中第 jjj 个商品需要花费小鹿 cjc_jcj​ 元(1≤c1≤c2≤⋯≤cm1\le c_1\le c_2\le \cdots \le c_m1≤c1​≤c2​≤⋯≤cm​),对于第 iii 个朋友,小鹿要么给这个朋友从商店里买一个编号为jjj 的商品, 其中1≤j≤ki1\le j\le k_i1≤j≤ki​,花费 cjc_jcj​ 元,要么直接给这位朋友 ckic_{k_i}cki​​ 元,而不需要购买礼物。小鹿是一个很节省的好孩子,她想知道最少需要花费多少钱?

1≤t≤103,1≤n,m≤3⋅105,1≤ki≤m,1≤c1≤c2≤…≤cm≤1091 \leq t \leq 10^3,1 \leq n, m \leq 3 \cdot 10^5,1 \leq k_i \leq m,1 \le c_1 \le c_2 \le \ldots \le c_m \le 10^91≤t≤103,1≤n,m≤3⋅105,1≤ki​≤m,1≤c1​≤c2​≤…≤cm​≤109

其中保证所有的测试数据中,每次的测试数据的所有 casecasecase 的 nnn 之和以及 mmm 之和都不超过 3×1053\times10^53×105。

Solution

我们发现本题的解题关键在于,所有的商品的价格是从小到大递增的,读懂题意以后,我们就可以自然地发现当无法购买商品,也就是就那么几个,卖完了之后, kik_iki​ 小的,选择第二种方法,花费 ckic_{k_i}cki​​ ,不占用宝贵的商品资源,是一定比 kik_iki​ 大的选择方法二总花费更少,所以 kik_iki​ 小的就放到后面最后选,因为他有保底。让 kik_iki​ 大的先选,先买前面便宜的商品,不得已的时候才直接送钱。这样花费最小。所以就是按照每个人的编号 kik_iki​ 从大到小排序,然后贪心模拟即可。

Code

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;
const int N = 5e5 + 7, mod = 1e9 + 7;
typedef long long ll;
int n, m, t, x;
int k[N], c[N];int main()
{scanf("%d", &t);while(t -- ) {ll ans = 0;scanf("%d%d", &n, &m);for(int i = 1; i <= n; ++ i) {scanf("%d", &k[i]);}for(int i = 1; i <= m; ++ i) {scanf("%d", &c[i]);}sort(k + 1, k + 1 + n);reverse(k + 1, k + 1 + n);int now = 1;for(int i = 1; i <= n; ++ i) {if(c[k[i]] <= c[now]) {ans += c[k[i]];}else ans += c[now], now ++ ;}printf("%lld\n", ans);}return 0;
}

Code by tourist

/***    author:  tourist*    created: 05.01.2021 17:34:59
**/
#include <bits/stdc++.h>using namespace std;int main() {ios::sync_with_stdio(false);cin.tie(0);int tt;cin >> tt;while (tt--) {int n, m;cin >> n >> m;vector<int> k(n);for (int i = 0; i < n; i++) {cin >> k[i];}sort(k.begin(), k.end());vector<int> c(m);for (int i = 0; i < m; i++) {cin >> c[i];}int low = 0, high = min(n, m);while (low < high) {int mid = (low + high + 1) >> 1;bool ok = true;for (int i = 0; i < mid; i++) {if (k[n - mid + i] <= i) {ok = false;break;}}if (ok) {low = mid;} else {high = mid - 1;}}long long ans = 0;for (int i = 0; i < n; i++) {ans += c[k[i] - 1];}long long best = ans;for (int i = 1; i <= low; i++) {ans -= c[k[n - i] - 1];ans += c[i - 1];best = min(best, ans);}cout << best << '\n';}return 0;
}

可以看到 tourist 打的是一个带反悔的贪心

D - Strange Definition

Problem D Strange Definition

两个整数 xxx 和 yyy 是 相邻的 当 lcm(x,y)gcd(x,y)\cfrac{lcm(x,y)}{gcd(x,y)}gcd(x,y)lcm(x,y)​是完全平方数。

你有一个长度为 nnn 的序列 aaa。每一秒,所有的 aia_iai​都会变成序列中所有和它 相邻的 的数的乘积。令 did_idi​为数列中与 aia_iai​ 相邻的 的数的个数

有 qqq 次询问,每次询问给出一个 www,求在 www 秒时的 max⁡{di}\max\{d_i\}max{di​}

Solution

首先,我们来学习一个单词:perfectsquare\tt perfect\ squareperfect square:完全平方数…我还以为是完美的正方形,我说哪来的正方形…给我人看傻了…

以及 adjacent\tt adjacentadjacent: adj. 相邻的,毗邻的

然后开始照例分析题目中的性质。

首先是完全平方数,就是指整数 xxx 存在一个正整数 yyy ,使得 x=y2x=y^2x=y2,整数 xxx 就是一个完全平方数。

我们定义两个数是相邻的,是指两个整数 xxx 和 yyy ,满足: lcm(x,y)gcd(x,y)\cfrac{lcm(x,y)}{gcd(x,y)}gcd(x,y)lcm(x,y)​是完全平方数。

我们知道 gcd⁡(x,y)×lcm(x,y)=x×y\gcd(x,y) \times lcm(x,y)=x\times ygcd(x,y)×lcm(x,y)=x×y。

lcm(x,y)gcd(x,y)=x×ygcd⁡(x,y)2\cfrac{lcm(x,y)}{gcd(x,y)}=\cfrac{x\times y}{\gcd(x,y)^2}gcd(x,y)lcm(x,y)​=gcd(x,y)2x×y​

这个东西是完全平方数,也就意味着它可以被开根。

即 x×ygcd⁡(x,y)2=x×ygcd⁡(x,y)\sqrt{\cfrac{x\times y}{\gcd(x,y)^2}}=\cfrac{\sqrt{x\times y}}{\gcd(x,y)}gcd(x,y)2x×y​​=gcd(x,y)x×y​​

也就是说对于任意两个整数 xxx 和 yyy 而言,若 x×yx\times yx×y 是完全平方数,则这两个数是相邻的。

数论必备 唯一分解定理得:

x=p1α1×p2α2×⋯×pkαkx=p_1^{\alpha_1}\times p_2^{\alpha_2} \times \cdots\times p_{k}^{\alpha_{k}}x=p1α1​​×p2α2​​×⋯×pkαk​​

y=p1β1×p2β2×⋯×pkβky=p_1^{\beta_1}\times p_2^{\beta_2} \times \cdots\times p_{k}^{\beta_{k}}y=p1β1​​×p2β2​​×⋯×pkβk​​

x×y=p1α1+β1×p2α2+β2×⋯×pkαk+βkx\times y=p_1^{\alpha_1+\beta_1}\times p_2^{\alpha_2+\beta_2} \times \cdots\times p_{k}^{\alpha_k+\beta_{k}}x×y=p1α1​+β1​​×p2α2​+β2​​×⋯×pkαk​+βk​​

显然,若两个数的乘积是完全平方数,则所有质因子的次幂 α+β\alpha +\betaα+β 均为偶数。

也就意味着所有质因子对应的 α\alphaα 和 β\betaβ 是同奇偶性的,即 α\alphaα 是奇数, β\betaβ 也是奇数,或者 α\alphaα 是偶数, β\betaβ 也是偶数,或者一个是偶数,一个是 000 ,这样二者相加才是偶数。

因为我们发现还有 000 的存在,所以我们需要考虑如何把偶次幂与偶次幂,和 000 次幂与偶次幂表示成一个形式。很容易就想到了我们判断奇偶性的时候使用的 % 2,这样偶次幂或者本来就是 000 的最后都会变成 000 ,而奇次幂最后会变成 111 所以我们将每个数都质因数分解的同时,对于每一个质因子的次幂都 % 2 ,也就是质因数分解的时候指数一直 /= 2 也就是 /=i2/= i^2/=i2 。最后将 x=p1α1×p2α2×⋯×pkαkx=p_1^{\alpha_1}\times p_2^{\alpha_2} \times \cdots\times p_{k}^{\alpha_{k}}x=p1α1​​×p2α2​​×⋯×pkαk​​ 变为 x=p11×p20×⋯×pk0x=p_1^{1}\times p_2^{0} \times \cdots\times p_{k}^{0}x=p11​×p20​×⋯×pk0​ 的形式。

这样,最后只要是 000 就都是一组。

但是对于奇数而言,不一定。因为虽然都是是 111 ,但是奇次幂的话,必须能够配对,才是一组,配对的意思就是 变成 x=p11×p20×⋯×pk0x=p_1^{1}\times p_2^{0} \times \cdots\times p_{k}^{0}x=p11​×p20​×⋯×pk0​ 的形式以后,两个数剩余的质因子必须完全相同,也就是拥有相同的 111 次幂的质因子,也就是转换为上面的那个形式以后, 两个数的值完全相同。

我们可以使用 map 来储存同一组(相互都相邻)的元素个数。

分析完相邻的性质以后,我们知道如何判断是否相邻,我们可以直接对于每个输入的数,判断一下,存入 map 中。

然后再来考虑第二个问题,题目中的操作,合并。

我们从第 000 秒开始,每一秒,每一个数都会变成所有与自己相邻(包括自己)的数的乘积,然后有 qqq 次询问,每次会问第 ω\omegaω 秒,数列中,所有相邻组(全部两两相邻)中元素个数最多的个数。ω≤1018\omega \le10^{18}ω≤1018。所以我们瞬间就可以看出点什么东西,就是这个操作修改以后,答案肯定存在一个循环节或者之后的某一时刻就不会再发生变化了。因为询问的数据太大, 啥数据结构都不可能存得下,也不可能快速地求解。

然后我们来分析这个操作,同一组的所有的数都会同时合并,我们根据上面的分析,知道一共有四种组别

  1. 个数为偶数的 000 组(偶次幂)
  2. 个数为奇数的 000 组(偶次幂)
  3. 个数为偶数的 111 组(奇次幂)
  4. 个数为奇数的 111 组(奇次幂)

我们来分析这四种组别合并后会发生什么。

  • 第一种,第二种

我们的合并操作是全部数的乘积,对于质因数分解后的 xxx ,全部数的乘积实际上就是所有数的指数的和,而第一种,第二种,不管组内个数是奇数个还是偶数个,因为都是偶数,所以和仍然是偶数,也意味着还是第一种或者第二种,而第一种和第二种实际上是同一种,因为都是相邻的,这里分开只是两种不同的情况。所以第一第二种任意秒以后,仍然还是第一第二种。

  • 第三种

偶数个全部对应的奇次幂,和为偶数,因为每个次幂都是奇数,也就是 111 ,奇数加奇数为偶数,在第一秒第一次合并以后归为第一种或者第二种。

  • 第四种

奇次幂,还是奇数个,那么相乘,也就是指数相加,永远都还是第四种。

分析完我们发现,询问一共分 ω=0\omega=0ω=0 和 ω>0\omega>0ω>0 两种情况, ω=0\omega=0ω=0 的时候,数列中的数一共分为三种

  • 第一种就是我们上面说的第一第二种,我们累计所有指数是偶次幂的元素的个数。
  • 第二种为我们上面说的第三种,即虽然是 111 组,但是个数是偶数,也就是我们 map 存的第二维是偶数。
  • 第三种就是我们上面说的第四种,奇数个的奇数次幂,我们用 map 来hash一下,对于这一种的每一个值,都分为每一组,取最值。

ω=0\omega=0ω=0 时答案就是上述三种的元素个数的最大值。

ω>0\omega>0ω>0 时, 第二种全部归为第一种,所以我们只需要用第三种里最大的那一组的个数,与第一种元素个数加上第二种的元素个数,取最大值即可。

detail

虽然上面说了那么大一堆,这些实际上只是我当时做题的时候的心理活动,在演草纸上画思维导图来分类讨论,总共花了不到 101010 分钟,因为写题解我习惯讲的比较详细,我希望可以把如何思考问题带给大家,所以内容有点多了,请大家见谅 …

Code

实现就非常简单了,照着上面的思路模拟一遍就行了

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <unordered_map>using namespace std;
const int N = 3e5 + 7, mod = 1e9 + 7;
typedef long long ll;
unordered_map<int, int> Hash;
void get_factor(int x)
{for(int i = 2; ; ++ i) {int even = i * i;while(x % even == 0 && x >= even) x /= even;if(x < even) break;}Hash[x] ++ ;我们实际上只需要知道它是奇还是偶//x就是把所有的质因子的次方全部压成0或者1,0就是偶次幂,x=1(被除尽了),1次就是奇次幂。x=若干一次质因子的乘积//偶次幂无所谓,奇次幂必须所含奇次幂的质因子全部完全相同才是一组(能凑成偶数,也就是完全平方数)
}
int n, m, t, q;
int main()
{scanf("%d", &t);while(t -- ) {Hash.clear();scanf("%d", &n);for(int i = 1; i <= n; ++ i) {int a;scanf("%d", &a);get_factor(a);}int ans1 = 0, ans2 = 0;for(auto it = Hash.begin(); it != Hash.end(); ++ it) {//0秒的情况不变ans1 = max(ans1, it -> second);//second是个数,0秒没有变化的时候,最大数量的那组就是最大的那组//1秒的情况合并if(it -> second % 2 == 0 || it -> first == 1)//(组内有偶数个 || 这个组是偶数次幂组)ans2 += it->second;//1秒钟后就会全部归为偶组}ans2 = max(ans2, ans1);//取奇数组和偶数组中的最大值scanf("%d", &q);for(int i = 1; i <= q; ++ i) {ll w;scanf("%lld", &w);if(w == 0)printf("%d\n", ans1);else printf("%d\n", ans2);}}return 0;
}

Code by tourist

/***    author:  tourist*    created: 05.01.2021 17:43:58
**/
#include <bits/stdc++.h>using namespace std;int main() {ios::sync_with_stdio(false);cin.tie(0);const int MAX = (int) 1e6 + 10;vector<int> h(MAX, 1);for (int i = 1; i < MAX; i++) {int tmp = i;for (int j = 2; j * j <= tmp; j++) {if (tmp % j == 0) {int cnt = 0;while (tmp % j == 0) {cnt += 1;tmp /= j;}if (cnt % 2 == 1) {h[i] *= j;}}}if (tmp > 1) {h[i] *= tmp;}}int tt;cin >> tt;while (tt--) {int n;cin >> n;vector<int> a(n);for (int i = 0; i < n; i++) {cin >> a[i];a[i] = h[a[i]];}sort(a.begin(), a.end());vector<int> b = a;int ans0 = 0;int beg = 0;while (beg < n) {int end = beg;while (end + 1 < n && a[end + 1] == a[end]) {end += 1;}ans0 = max(ans0, end - beg + 1);if ((end - beg + 1) % 2 == 0) {for (int i = beg; i <= end; i++) {b[i] = 1;}}beg = end + 1;}sort(b.begin(), b.end());int ans1 = 0;beg = 0;while (beg < n) {int end = beg;while (end + 1 < n && b[end + 1] == b[end]) {end += 1;}ans1 = max(ans1, end - beg + 1);beg = end + 1;}int q;cin >> q;while (q--) {long long w;cin >> w;cout << (w == 0 ? ans0 : ans1) << '\n';}}return 0;
}

可以看到大体的思路是相同的,但是在实现上,tourist 写了一个离散化的映射,而没有选择map,导致代码有点乱 … 反正看不懂就对了 ~

E - Strange Shuffle

Problem E Strange Shuffle

交互题,题目大意就是有 nnn 个人, 111 ~ nnn, 围成一个圈,每个人手里有 kkk 张牌,从第 111 秒开始,每一秒,所有的人,都将自己手中的牌的一半上取整,即 ⌈k2⌉\lceil\frac{k}{2}\rceil⌈2k​⌉ 给自己右边的人,与此同时,把自己手中的牌的一半下取整,即⌊k2⌋\lfloor\frac{k}{2}\rfloor⌊2k​⌋ 给自己左边的人,但是有一个家伙比较特殊,他每次会把自己的全部的牌都给自己右边的人。你可以询问 q≤1000q\le1000q≤1000 次,每次询问一个数 xxx,会回答你第 xxx 个人手里的牌的数量。
1≤n≤105,1≤k≤1091\le n\le10^5,1\le k \le10^91≤n≤105,1≤k≤109

Solution

我们可以先打表找规律。三分钟写一个暴力的打表程序:

int n =  60;
int a[N], b[N];
int main()
{for(int i = 0; i < n; ++ i) {a[i] = 4;}int k = 15;int t = 10;while(t -- ) {memset(b, 0, sizeof b);for(int i = 0; i < n; ++ i) {if(i == k) {b[k + 1] += a[k];a[k] = 0;continue;}int l = (i - 1 + n) % n;b[l] += floor((double)a[i] / 2);int r = (i + 1) % n;b[r] += ceil((double)a[i] / 2);a[i] = 0;}for(int i = 0; i < n; ++ i)a[i] += b[i], printf("%d ", a[i]);puts("");}
}


好的,发现规律了,规律就是所有的数字,如果没有那个特殊的人来捣鬼,就不会发生任何变化,而那个捣鬼的人周围的数字,左边,会越来越小,右边会越来越大。所以我们只需要暴力找到 “疫区”(也就是发生变化的区域),然后如果大于 kkk 就往左找,如果小于 kkk 就往右找,最后找到那个疫区里唯一一个大小等于 kkk 的数字,就是那个特殊的人。

如何暴力找? n≤105n\le 10^5n≤105 ,直接 for 循环看上去会超时,因为题目仅能询问 100010001000 次,那么考虑优化。首先我们最直观的走路优化加速的方法就是倍增。但是倍增会跳的非常的快,而且关键在于我们遇见一个 kkk 不知道它到底是在疫区左边还是疫区右边,因为疫区还没有扩展出来,我们可能就已经跳过疫区了,这样可能需要来回找,有点麻烦,但是可做。考虑慢一点,我发现,疫区是一个一个向外扩展,每次 +1+1+1 ,我们考虑一个慢一点的走路加速方式:等差数列。我们 for 循环,cur 指针指向当前的位置,每次走 iii 步,这样走 1,2,⋯n1,2,\cdots n1,2,⋯n 步,这样走完一遍,最多需要走 xxx 步,其中 x=n(n+1)2,n≤105x=\frac{n(n+1)}{2},n\le10^5x=2n(n+1)​,n≤105,x≤105≈330x\le \sqrt{10^5}≈330x≤105​≈330。而我们最多可以询问 100010001000 次,完全没问题。那么会不会第一次找不到呢,我们可以很直观的发现第一次走,一定能找到,因为我们的指针 curcurcur 和疫区扩展的速度是一样的,所以第一次就能找到。

比赛的时候可以一个人写打表程序,一个人推规律,其实本题的规律非常简单,可以不打表也能看出来。因为我们知道一个数,除以二上取整,可能会大于单纯的除以二,除以二下取整,可能会小于,单纯的除以二,而题目中不同的人,最开始手里的牌都一样,他们给两边的人牌,两边的人也给他牌,正常人手里的牌的个数是不变的,而特殊的人的手里的牌,因为会全部给到右边的人手里,右边的人手里的牌多了之后,更右边的人手里的牌也会多,会一直多下去,题目中为了能保证这一点,特别增加了上取整来实现这一操作。同理,左边也会一直小,一直变小,而中间的那一位,因为给全部给右边, 而右边因为只多了一半,再还给左边那位特殊的人的时候,是要下取整,这样那个特殊的人手里的牌会一直不变,也就一直保持为 kkk,这一点是本题解题的关键。

Code

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <unordered_map>using namespace std;
const int N = 3e5 + 7, mod = 1e9 + 7;
typedef long long ll;
typedef int itn;
int res;
int n, m, k;int ask(int x)
{printf("? %d\n", x);fflush(stdout);int res;scanf("%d", &res);return res;
}int main()
{scanf("%d%d", &n, &k);int cur = 1;ask(cur);for(int i = 1; i <= n; ++ i) {cur = (cur + i - 1) % n + 1;//!res = ask(cur);if(res != k) {//找到了疫区if(res > k) {//右边,往左找while(true) {cur = (cur == 1 ? n : cur - 1);res = ask(cur);if(res == k) {printf("! %d\n", cur);fflush(stdout);return 0;}}}else {while(true) {cur = (cur == n ? 1 : cur + 1);res = ask(cur);if(res == k) {printf("! %d\n", cur);fflush(stdout);return 0;}}}}}return 0;
}

Code by tourist

/***    author:  tourist*    created: 05.01.2021 18:11:34
**/
#undef LOCAL#include <bits/stdc++.h>using namespace std;template <typename A, typename B>
string to_string(pair<A, B> p);template <typename A, typename B, typename C>
string to_string(tuple<A, B, C> p);template <typename A, typename B, typename C, typename D>
string to_string(tuple<A, B, C, D> p);string to_string(const string& s) {return '"' + s + '"';
}string to_string(const char* s) {return to_string((string) s);
}string to_string(bool b) {return (b ? "true" : "false");
}string to_string(vector<bool> v) {bool first = true;string res = "{";for (int i = 0; i < static_cast<int>(v.size()); i++) {if (!first) {res += ", ";}first = false;res += to_string(v[i]);}res += "}";return res;
}template <size_t N>
string to_string(bitset<N> v) {string res = "";for (size_t i = 0; i < N; i++) {res += static_cast<char>('0' + v[i]);}return res;
}template <typename A>
string to_string(A v) {bool first = true;string res = "{";for (const auto &x : v) {if (!first) {res += ", ";}first = false;res += to_string(x);}res += "}";return res;
}template <typename A, typename B>
string to_string(pair<A, B> p) {return "(" + to_string(p.first) + ", " + to_string(p.second) + ")";
}template <typename A, typename B, typename C>
string to_string(tuple<A, B, C> p) {return "(" + to_string(get<0>(p)) + ", " + to_string(get<1>(p)) + ", " + to_string(get<2>(p)) + ")";
}template <typename A, typename B, typename C, typename D>
string to_string(tuple<A, B, C, D> p) {return "(" + to_string(get<0>(p)) + ", " + to_string(get<1>(p)) + ", " + to_string(get<2>(p)) + ", " + to_string(get<3>(p)) + ")";
}void debug_out() { cerr << endl; }template <typename Head, typename... Tail>
void debug_out(Head H, Tail... T) {cerr << " " << to_string(H);debug_out(T...);
}#ifdef LOCAL
#define debug(...) cerr << "[" << #__VA_ARGS__ << "]:", debug_out(__VA_ARGS__)
#else
#define debug(...) 42
#endifint main() {ios::sync_with_stdio(false);cin.tie(0);int n, k;cin >> n >> k;vector<int> a(n, k);vector<int> b(n);#ifdef LOCALmt19937 rng((unsigned int) chrono::steady_clock::now().time_since_epoch().count());int secret = rng() % n;debug(secret);int Q = 0;#endifauto Ask = [&](int pos) {#ifdef LOCALQ += 1;int res = a[pos];for (int i = 0; i < n; i++) {b[i] = 0;}for (int i = 0; i < n; i++) {int R = (i + 1) % n;int L = (i + n - 1) % n;if (i == secret) {b[R] += a[i];} else {b[L] += a[i] / 2;b[R] += (a[i] + 1) / 2;}}swap(a, b);return res;#elsecout << "? " << pos + 1 << endl;int foo;cin >> foo;return foo;#endif};const int WAIT = 500;for (int i = 0; i < WAIT; i++) {Ask(0);}int step = (n + 399) / 400;int L = -1;int R = -1;for (int i = 0; i < n; i += step) {int x = Ask(i);if (x > k) {L = i - (n + 1) / 2;R = i;while (L + 1 < R) {int M = L + (R - L) / 2;x = Ask((M % n + n) % n);if (x > k) {R = M;} else {L = M;}}int res = (L % n + n) % n;cout << "! " << res + 1 << endl;#ifdef LOCALdebug(res);debug(Q);assert(res == secret);#endifreturn 0;}if (x < k) {L = i;R = i + (n + 1) / 2;while (L + 1 < R) {int M = L + (R - L) / 2;x = Ask((M % n + n) % n);if (x < k) {L = M;} else {R = M;}}int res = (R % n + n) % n;cout << "! " << res + 1 << endl;#ifdef LOCALdebug(res);debug(Q);assert(res == secret);#endifreturn 0;}}assert(false);return 0;
}

看不懂就对了

F - Strange Housing

Problem F Strange Housing

给定一个 nnn 个节点,mmm 条无向边的图,现在你要给一些点染色,使得:

  • 一条边所连接的两个点不能都被染色。
  • 在所有连接两个不被染色的点的边都被删除的情况下,这个图满足任意两个点互相可达。

如果有染色方案满足上述要求,输出一行 YES 之后输出要染色的点的数量,并升序输出所有被染色的点的编号;否则输出一行 NO

TTT 组询问。

1≤T≤105;2≤n≤3×105;0≤m≤3×105.1\leq T\leq 10^5;2\leq n\leq 3\times10^5;0\leq m\leq 3\times10^5.1≤T≤105;2≤n≤3×105;0≤m≤3×105.∑n,∑m≤3×105\sum n,\sum m\leq 3\times10^5∑n,∑m≤3×105 .

Solution

又是一个非常简单的题目 ~ 很明显,如果图是一个连通图的话,很明显他一定可以满足题目中的条件,因为一个连通图一定存在一个生成树,那么树的点,染色法,一层染,一层不染,一定能构造出来一种答案。所以我们只需要去判断图是否连通,不连通那肯定为 No

所以我们可以直接使用并查集来判断是否为连通图,然后用 dfsdfsdfs 染色即可。染色的时候,我们对于当前点,如果没有被遍历过,就给他染上色,然后对于他的所有邻点,都判断一下是否被同时染色,如果有一个点在它之前被染上色了,那就把它自己染上的色去掉,因为我们大不了就把这条边删掉,而点是一定不能同色的,原来的点已经确定好了不能改,当前点可以改,因为当前点染色也是试的 ~

判断完染色的正确性以后,再对于所有的没有遍历过的子节点,dfs重复上述动作即可。至于升序输出点的编号,我们只需要从小到大枚举即可。

Code

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <unordered_map>using namespace std;
const int N = 5000007, M = 5000007, mod = 1e9 + 7;
typedef long long ll;
typedef int itn;
int res;
int n, m, k, t;
int head[N], ver[M], nex[M], tot;
int vis[N], col[N], fa[N];void add(int x, int y)
{ver[tot] = y;nex[tot] = head[x];head[x] = tot ++ ;
}int Find(int x)
{if(fa[x] == x) return x;return fa[x] = Find(fa[x]);
}void dfs(int x, int father)
{vis[x] = 1;col[x] = !col[father];for(int i = head[x]; ~i; i = nex[i]) {//先确定完int y = ver[i];if(vis[y]) {if(col[y]) col[x] = 0;}}for(int i = head[x]; ~i; i = nex[i]) {//再走int y = ver[i];if(vis[y] == 0 && y != father) {dfs(y, x);}}
}int main()
{scanf("%d", &t);while(t -- ) {tot = 0;scanf("%d%d", &n, &m);for(int i = 1; i <= n; ++ i) {vis[i] = 0, fa[i] = i;head[i] = -1, col[i] = 0;}int cnt = n;for(int i = 1; i <= m; ++ i) {int x, y;scanf("%d%d", &x, &y);add(x, y);if(Find(x) != Find(y)) {cnt -- ;fa[Find(x)] = fa[y];}add(x, y), add(y, x);}if(cnt != 1) {//连了 n-1 次就是一棵树puts("NO");continue;}puts("YES");dfs(1, 0);cnt = 0;vector<int> ans;for(int i = 1; i <= n; ++ i) {if(col[i]) {ans.push_back(i);cnt ++ ;}}printf("%d\n", cnt);for(auto x : ans)printf("%d ", x);puts("");}return 0;
}

Code by tourist

/***    author:  tourist*    created: 05.01.2021 18:05:09
**/
#include <bits/stdc++.h>using namespace std;int main() {ios::sync_with_stdio(false);cin.tie(0);int tt;cin >> tt;while (tt--) {int n, m;cin >> n >> m;vector<vector<int>> g(n);for (int i = 0; i < m; i++) {int x, y;cin >> x >> y;--x; --y;g[x].push_back(y);g[y].push_back(x);}vector<int> res(n, -1);res[0] = 1;vector<int> que;for (int x : g[0]) {que.push_back(x);res[x] = 0;}for (int b = 0; b < (int) que.size(); b++) {for (int v : g[que[b]]) {if (res[v] == -1) {res[v] = 1;for (int x : g[v]) {if (res[x] == -1) {res[x] = 0;que.push_back(x);}}}}}if (*min_element(res.begin(), res.end()) == -1) {cout << "NO" << '\n';continue;}cout << "YES" << '\n';vector<int> ret;for (int i = 0; i < n; i++) {if (res[i] == 1) {ret.push_back(i);}}cout << ret.size() << '\n';for (int i = 0; i < (int) ret.size(); i++) {if (i > 0) {cout << " ";}cout << ret[i] + 1;}cout << '\n';}return 0;
}

G - Strange Permutation

给出一个 111 到 nnn 的排列 p1…np_{1\dots n}p1…n​ 。你可以选择若干个互不重叠的区间,并将它们翻转,称为一组翻转操作。翻转一个区间 [l,r][l,r][l,r] 的代价是 r−lr - lr−l。一组翻转的代价是所选区间的代价之和。你希望花费的代价不超过 ccc。

每组可能的翻转操作,都会得到一个排列。将这些排列按字典序从小到大排序。

你需要回答 qqq 次询问。每次询问有两个参数 i,ji,ji,j,表示问字典序第 jjj 小的排列里,第 iii 个位置上的数是几。如果不存在 jjj 个排列,则输出 −1-1−1。

数据范围:1≤n≤3×1041\leq n\leq 3\times10^41≤n≤3×104 ,1≤c≤41≤c≤4,1≤q≤3×1051\leq c\leq 41≤c≤4,1\leq q\leq 3\times 10^51≤c≤41≤c≤4,1≤q≤3×105 。

Solution

有点难的题了,用到了隔板法,还是直接放官方题解吧,我就是学的这个…

Code by tourist

/***    author:  tourist*    created: 05.01.2021 18:25:48
**/
#include <bits/stdc++.h>using namespace std;int main() {ios::sync_with_stdio(false);cin.tie(0);int tt;cin >> tt;while (tt--) {int n, c, q;cin >> n >> c >> q;vector<int> p(n);for (int i = 0; i < n; i++) {cin >> p[i];--p[i];}vector<vector<long long>> ways(n + 1, vector<long long>(c + 1));for (int j = 0; j <= c; j++) {ways[n][j] = 1;}for (int i = n - 1; i >= 0; i--) {for (int j = 0; j <= c; j++) {ways[i][j] = ways[i + 1][j];for (int k = 1; k <= j; k++) {if (i + k < n) {ways[i][j] += ways[i + k + 1][j - k];}}}}vector<vector<long long>> sumL(c + 1, vector<long long>(n + 1));vector<vector<long long>> sumR(c + 1, vector<long long>(n + 1));for (int rm = 0; rm <= c; rm++) {for (int i = 0; i < n; i++) {sumL[rm][i + 1] = sumL[rm][i];sumR[rm][i + 1] = sumR[rm][i];for (int k = 1; k <= rm; k++) {if (i + k < n) {if (p[i] < p[i + k]) {sumR[rm][i + 1] += ways[i + k + 1][rm - k];} else {sumL[rm][i + 1] += ways[i + k + 1][rm - k];}}}}}vector<int> order;while (q--) {int pos;long long id;cin >> pos >> id;--pos; --id;if (id >= ways[0][c]) {cout << -1 << '\n';continue;}int ans = -1;int i = 0;int rm = c;while (i < n) {assert(id < ways[i][rm]);int low = i, high = n;while (low < high) {int mid = (low + high + 1) >> 1;if (sumL[rm][mid] - sumL[rm][i] > id || sumR[rm][mid] - sumR[rm][i] > ways[i][rm] - 1 - id) {high = mid - 1;} else {low = mid;}}if (pos < low) {ans = p[pos];break;}id -= sumL[rm][low] - sumL[rm][i];assert(id >= 0 && id < ways[low][rm]);i = low;order.clear();for (int k = 0; k <= rm; k++) {if (i + k < n) {order.push_back(k);}}sort(order.begin(), order.end(), [&](int x, int y) {return p[i + x] < p[i + y];});int shift = -1;for (int v : order) {long long cur = ways[i + v + 1][rm - v];if (id < cur) {shift = v;break;}id -= cur;}assert(shift != -1);assert(shift != 0);if (pos <= i + shift) {ans = p[i + (i + shift) - pos];break;}i += shift + 1;rm -= shift;}assert(ans != -1);cout << ans + 1 << '\n';}}return 0;
}

H - Strange Covering

给定二维平面上 nnn 个点,其中第 iii 个点用坐标 (xi,yi)(x_i,y_i)(xi​,yi​) 表示。现在你需要用两个边与坐标轴平行的矩形覆盖所有点,求矩形面积和最小值,点在矩形的边界上或边界内都算作被覆盖。一共有 TTT 组询问。

1≤T≤200000,1≤n≤200000,1≤∑n≤2000001\leq T\leq 200000,1\leq n\leq200000,1\leq \sum n\leq 2000001≤T≤200000,1≤n≤200000,1≤∑n≤200000.

0≤xi,yi≤1090\leq x_i,y_i\leq10^90≤xi​,yi​≤109 .

Solution

哇,这题比赛的时候没有人过,连 tourist 都没 AC ,一个不算太难的计算几何,但是需要分类讨论,太麻烦了,比赛的时候时间不够 ~




这题出的有点没意思了,这么麻烦的题一个小时以内怎么可能写的出来…

后记

妈呀,这样写题解有点太慢了,尽管我打字很快,但是写的东西太多了,比赛三小时,题解四小时,这谁受得了,下次我就写的简单一些吧呜呜呜

准备跟着 tourist 的步伐刷比赛,但是他的代码我看不懂呜呜呜

最后奉上官方题解链接,不过我的做法都没有参考官方题解hhh,看官方题解还是能得到一些知识的,毕竟这是他们出的题 ~

https://codeforces.com/blog/entry/86464

Codeforces Round #694 (Div. 1 + Div2)(A ~ H,8题全,超高质量题解)【每日亿题】2021/2/1、2/2相关推荐

  1. Codeforces Round #704 (Div. 2)(A ~ E)5题全 超高质量题解【每日亿题2 / 23】

    整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 目录 A.Three swimmers B.Card Deck C.Maximum width D.G ...

  2. (6/6) Codeforces Round #694 (Div. 2)

    (6/6) Codeforces Round #694 (Div. 2) A. Strange Partition 题意: 给一个数组,数组中的所有元素可以任意合并,求数组的每个元素除以x上去整的和, ...

  3. Codeforces Round #694 Div. 2

    Codeforces Round #694 Div. 2 CodeForces 1471A Strange Partition CodeForces 1471B Strange List CodeFo ...

  4. Codeforces Round #694 (Div. 1) 部分简要题解

    A - Strange Birthday Party 越大的人应该获得更小价值的礼物. 证明:有两个人,其中,有两个礼物价值分别是,其中.当分别获得礼物,付出的代价是.当分别获得礼物,付出的代价是. ...

  5. Codeforces Round #694 (Div. 2) E. Strange Shuffle 交互 + 思维分块

    link 题意: nnn个人围成一圈,一开始每个人都有kkk张卡片,每回合n−1n-1n−1个人会给左边⌊x2⌋\left \lfloor \frac{x}{2} \right \rfloor⌊2x​ ...

  6. Codeforces Round #694 (Div. 2) D. Strange Definition 质因子分解 + 平方数

    传送门 题意: 定义相邻数为lcm(x,y)gcd(x,y)\frac{lcm(x,y)}{gcd(x,y)}gcd(x,y)lcm(x,y)​是一个平方数,则xxx和yyy是相邻的.现在给出q个询问 ...

  7. Codeforces Round #694 (Div. 2) F. Strange Housing (贪心思维)

    F. Strange Housing 题意 有 nnn 个点和 mmm 条边,对点进行染色.要求一条边的两个点不能都染色,并且删除两端都没有染色的边之后,图连通.请给出一种染色方案. 题解 暴力贪心即 ...

  8. 【Codeforces Round #853 (Div. 2)】C. Serval and Toxel‘s Arrays【题解】

    题目 Toxel likes arrays. Before traveling to the Paldea region, Serval gave him an array a a a as a gi ...

  9. 洛谷 2 月月赛 I 『MdOI R4』 (Div2) A ~ D 四题全,也许会有六题,超高质量题解 (Div.1E、F下辈子一定补)【每日亿题2 / 9】

    整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 目录 A.P7337 『MdOI R4』Fun B.P7338 『MdOI R4』Color C.P7 ...

最新文章

  1. 求未知数X最临近的能被某个数字N整除的数
  2. C#Winform+WindowsAPI做个剪贴板无缝自动保存器(视频截图利器)
  3. BZOJ 1901 Dynamic Rankings(线段树+treap)
  4. 【转】深入了解CPU两大架构ARM与X86
  5. Recover it!
  6. 飞哥:程序员完全没时间提升自己该怎么办?
  7. 哪一个不是linux常用的shell,Linux下查看使用的是哪种shell的方法汇总
  8. JavaScript 正则表达式(RegExp对象、属性、方法、String支持)
  9. 【PHP源码】二维码生成api
  10. python中条件、循环等
  11. 在微博中应用PageRank算法
  12. oracle在cmd中启动数据库实例
  13. Tarjan 算法解决 LCA 问题
  14. 大数据技术Spark详解
  15. python网球比赛模拟_【python】羽毛球竞技模拟
  16. AUTOSAR DCM
  17. 独家秘技||如何快速入门一个陌生知识领域?
  18. Core Data详解
  19. 给你一份完整的Web前端学习路线图
  20. linux版本、查找、重启等命令

热门文章

  1. 阿里、百度、字节跳动、京东、地平线等计算机视觉实习生面试经历分析,已成功上岸!...
  2. (CV方向)精通C++,该如何学?
  3. 最新!字节跳动再次扩招1000人,招聘要求令人窒息
  4. Xcode10:Implicit declaration of function '' is invalid in C99
  5. SQL Server 2014聚集列存储索引
  6. Tomcat 源码阅读记录(1)
  7. 管理 Oracle Solaris ZFS 存储池网址
  8. 习惯几乎可以绑住一切,只是不能绑住偶然。比如那只偶然尝了鲜血的老虎。...
  9. stdio.h头文件中申明的基本函数
  10. 单链表中一个插入操作的分析