CF刷题(03)——难度2100~2400

news/2024/11/30 18:41:44/

这个博客记录2100到2400共17个题

2100

1.B. Maximum Value

  • 题意:You are given a sequence a consisting of n n n integers. Find the maximum possible value of (integer remainder of a i a_i ai divided by a j a_j aj), where 1 ≤ i , j ≤ n 1 ≤ i, j ≤ n 1i,jn and a i ≥ a j a_i ≥ a_j aiaj.
  • 看清,限制条件是 a i ≥ a j a_i \ge a_j aiaj.
  • 其实就是从 2 ∗ a 2*a 2a 2 ∗ m a x a 2*max_a 2maxa,每次增加 a a a,找到小于 k ∗ a k*a ka 的最大值,答案就在这些之中。
  • 还有一个 O ( 1 ) O(1) O(1) 的做法找到小于 k ∗ a k*a ka 的方法,看看这个,脑洞还挺大的。
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 2000010;
int A[maxn], B[maxn], N;
int main() {scanf("%d", &N);for (int i = 0; i < N; i++) {int x;scanf("%d", &x);A[x] = x;}for (int i = 1; i <= 2000000; i++) {B[i] = max(A[i], B[i - 1]);}int ans = 0;for (int i = 1; i <= 1000000; i++) {if (A[i]) {for (int j = 2 * i; j <= 2000000; j += i)  ans = max(ans, B[j - 1] % i);}}printf("%d\n", ans);
}
  • lower_bound()返回一个 iterator 它指向在[first,last)标记的有序序列中可以插入value,而不会破坏容器顺序的第一个位置,而这个位置标记了一个不小于value 的值。如果最后一个数比value还小就返回last。
  • 二分的方法,我的超时了,就是说 O ( N ∗ l o g N ∗ l o g M ) O(N * logN * logM) O(NlogNlogM) 的方法不行。

2.F. Ant colony

  • 题意:给一个数组,一段区间,问在区间中有多少个数字,是区间内所有数字的因数?
  • 想了想,其实用线段树很简单就能实现。每次只用看一段区间的最小值,以及最小值是否和这段区间的最大公约数相等,再存一下这段区间的最小值有几个。
  • 我发现,似乎线段树的题目,令query()函数都是返回node,这样子似乎可以解决一切问题,就算板子中有的query()函数是返回一个值,也可以改成返回node的板子。
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 100010;
int N, T;
ll a[maxn];
struct node {int l, r;ll d, minimum, nums;
}tr[maxn * 4];
ll gcd(ll a, ll b) {if (b == 0) return a;return gcd(b, a % b);
}
void pushup(node& u, node& l, node& r) {u.d = gcd(l.d, r.d);if (l.minimum < r.minimum) {u.minimum = l.minimum;u.nums = l.nums;}else if (l.minimum > r.minimum) {u.minimum = r.minimum;u.nums = r.nums;}else {u.minimum = l.minimum;u.nums = l.nums + r.nums;}
}
void pushup(int u) {pushup(tr[u], tr[2 * u], tr[2 * u + 1]);
}
void build(int u, int l, int r) {if (l == r) tr[u] = { l, r, a[l], a[l], 1 };else {tr[u].l = l, tr[u].r = r;int mid = (l + r) / 2;build(2 * u, l, mid), build(2 * u + 1, mid + 1, r);pushup(u);}
}
node query(int u, int l, int r) {if (l <= tr[u].l && tr[u].r <= r) return tr[u];int mid = (tr[u].l + tr[u].r) / 2;if (r <= mid) return query(2 * u, l, r);else if (l > mid) return query(2 * u + 1, l, r);else {node left = query(2 * u, l, r);node right = query(2 * u + 1, l, r);node res;pushup(res, left, right);return res;}
}
int main() {scanf("%d", &N);for (int i = 1; i <= N; i++) scanf("%lld", &a[i]);build(1, 1, N);scanf("%d", &T);while (T--) {int l, r;scanf("%d%d", &l, &r);node res = query(1, l, r);if (res.d == res.minimum) {printf("%lld\n", r - l + 1 - res.nums);}else {printf("%d\n", r - l + 1);}}return 0;
}

3.D. Segment Intersections

  • 两个区间集和,各有n个区间,第一个区间集和初始都为 [ l 1 , r 1 ] [ l 1 , r 1 ] [l1,r1],第二个区间集和初始都为 [ l 2 , r 2 ] [ l 2 , r 2 ] [l2,r2]。一次操作可以将区间 [ x , y ] [ x , y ] [x,y] 变成 [ x − 1 , y ] [ x − 1 , y ] [x1,y] 或者 [ x , y + 1 ] [ x , y + 1 ] [x,y+1]。求区间交集长度之和为 k k k 的最少操作次数。
  • 这个看上去情况很多,但是不要忘记n的范围很小,因此可以考虑暴力枚举的方式。主要是一个地方容易出现讨论,就是说,是先让下一对区间有交集,还是扩展上一个区间?
  • 我们可以换个思路讨论。枚举需要扩展的区间,从1枚举到n,然后就很好写了。
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
ll N, K, T, l1, l2, r1, r2;
int main() {cin >> T;while (T--) {cin >> N >> K >> l1 >> r1 >> l2 >> r2;ll ans = 1e18;ll lmax = max(l1, l2), rmax = max(r1, r2), lmin = min(l1, l2), rmin = min(r1, r2);K -= N * max(rmin - lmax, 0LL);if (K <= 0) ans = 0;else for (ll x = 1; x <= N; x++) {ll res = x * max(lmax - rmin, 0LL), max_get = x * (rmax - lmin - max(rmin - lmax, 0LL));if (max_get >= K) {res += K;}else {res += max_get + (K - max_get) * 2;}ans = min(ans, res);}cout << ans << endl;}
}

4.E. Restorer Distance

  • 我发现最近经常碰到这种题这种题,特点就是分类讨论的话情况异常复杂,但是题解又是一种略带暴力的感觉就搜索到了所有情况,而且自己一般都不怎么想得出来。
  • 题意:对于高度数组a[],目的是将所有高度平齐,有三种操作:1、a[i]–代价为R;2、a[i]++代价为A;3、将某一位置的一个单位转移到另一个位置 代价为M,问最小代价能平齐的高度;
  • 三分搜索出答案。
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 100010;
int N, a[maxn];
typedef long long ll;
ll A, R, M;
ll f(ll x) {ll lower = 0, upper = 0, res = 0;for (int i = 1; i <= N; i++) {if (a[i] > x) upper += a[i] - x;else lower += x - a[i];}if (M < A + R) {ll move = min(lower, upper);res += M * move;upper -= move, lower -= move;}res += A * lower + R * upper;return res;
}
int main() {cin >> N >> A >> R >> M;for (int i = 1; i <= N; i++) cin >> a[i];ll lb = 0, ub = 1e9 + 5;while (ub - lb > 1) {ll m1 = (lb + ub) / 2;ll m2 = (m1 + ub) / 2;ll f1 = f(m1), f2 = f(m2);if (f1 > f2) lb = m1;else ub = m2;}cout << min(f(lb), f(ub)) << endl;return 0;
}

5.D. Guess The Maximums

  • 题意:通过查询构造出答案要求的密码,每次查询可以询问数组A中指定的集合的最大值,最多可以查询12次,我们要构造的密码序列,对于一个密码Pi, Pi为除了Si这个集合中索引对应的数组A中的数字的最大值,Si是k个互不独立的集合。
  • 二分的次数一定要估算准确!用公式! ⌈ l o g 2 N ⌉ . \lceil log_2N\rceil. log2N.
  • 交互题果然好多都是二分查找。
  • 前十次,找出最大值所在的位置。第十一次,问最大值所在的那个集合的最大值。第十二次,问上述集合的补集的最大值。这样子答案是不是就出来了!
  • 我其实不太知道自己哪里写错了,但是这个二分写的异常复杂!但是网上的二分搜索写的就非常简单,因为他们是 [ 1 , k ] [1, k] [1,k] 之间定位最大值,用 lb 和 ub 去卡最大值,最后卡到数组的最后一个位置便是最大值的位置。
  • 这个确实有点裂开,他的输入有坑!可能不会把所有的集合的信息都给你,因为他认为给你k-1个集合就可以知道k个集合的信息了!我居然在这个地方错了一个小时!我还是一行一行对别人的代码才发现的这个问题!
  • 不过还是有收获的。最起码今天看到了交互题用二分去写的一个清爽的思路,之前那种讨论方式确实不可取。以及正数二分的一个新的写法,好像这种写法挺好用的。
#include<iostream>
#include<algorithm>
#include<vector>
#include<string>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 1010;
int belong[maxn], N, K, sz[maxn], ans[maxn];
string result;
int query(vector<int>& v) {printf("? %d", v.size());for (auto u : v) {printf(" %d", u);}printf("\n");fflush(stdout);int x;cin >> x;return x;
}
int main() {int T;cin >> T;while (T--) {cin >> N >> K;vector<int> q;memset(belong, 0, sizeof belong);for (int i = 1; i <= K; i++) {int C;cin >> C;sz[i] = C;for (int j = 1; j <= C; j++) {int idx;cin >> idx;belong[idx] = i;}}for (int i = 1; i <= N; i++) q.push_back(i);int mx = query(q);int lb = 1, ub = N;while (ub - lb >= 1) {int mid = (lb + ub) / 2;q.clear();for (int i = 1; i <= mid; i++) q.push_back(i);int xx = query(q);if (xx < mx) lb = mid + 1;else ub = mid;}int set_id = belong[lb];for (int i = 1; i < set_id; i++) ans[i] = mx;for (int i = set_id + 1; i <= K; i++) ans[i] = mx;q.clear();for (int i = 1; i <= N; i++) {if (belong[i] != set_id) q.push_back(i);}ans[set_id] = query(q);printf("! ");for (int i = 1; i <= K; i++) {printf("%d%c", ans[i], i == K ? '\n' : ' ');}fflush(stdout);cin >> result;if (result != "Correct") break;}return 0;
}
/*
3
4 2
2 1 3
2 2 4
4
2
3
3
Correct2 2
1 1
1 2
2
1
1
Correct3 2
1 1
2 2 3
3
2
3
1
Correct
*/

2200

1.D. Powerful array

  • 题意:给定一个数列: a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an。定义 K a K_a Ka 为区间 [ l , r ] [l,r] [lr] a a a 出现的次数。 t t t 个查询,每个查询l,r,对区间内所有 a [ i ] a[i] a[i] ,求 ∑ K 2 ∗ a [ i ] \sum{K^2*a[i]} K2a[i]
  • 用到了莫队算法:离线+分块。将n个数分成sqrt(n)块。对所有询问进行排序,排序标准:
  1. Q[i].left /block_size < Q[j].left / block_size (块号优先排序)
  2. 如果1相同,则 Q[i].right < Q[j].right (按照查询的右边界排序)
  • 如果一个数已经出现了x次,那么需要累加 ( 2 ∗ x + 1 ) ∗ a [ i ] (2*x+1)*a[i] (2x+1)a[i],因为 ( x + 1 ) 2 ∗ a [ i ] = ( x 2 + 2 ∗ x + 1 ) ∗ a [ i ] (x+1)^2*a[i] = (x^2 +2*x + 1)*a[i] (x+1)2a[i]=(x2+2x+1)a[i] x 2 ∗ a [ i ] x^2*a[i] x2a[i] 是出现x次的结果, ( x + 1 ) 2 ∗ a [ i ] (x+1)^2 * a[i] (x+1)2a[i] 是出现x+1次的结果。
  • 看了代码之后,妙啊!
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxa = 1000010, maxn = 200010;
typedef long long ll;
struct query {int l, r, id;
}Q[maxn];//sum[i] 是当前查到的 i 这个数字有多少个。
ll a[maxn], sum[maxa], ans[maxn];
int N, T, block_size, L, R;  //当前的左边界、右边界。
ll now;  //当前的答案bool cmp(query a, query b) {if (a.l / block_size == b.l / block_size) return a.r < b.r;return a.l / block_size < b.l / block_size;
}void modify(int l, int r) {//若当前 L 在 l 的左边while (L < l) {now -= (2 * sum[a[L]] - 1) * a[L];sum[a[L]]--;L++;}//若当前 L 在 l 的右边while (L > l) {L--;now += (2 * sum[a[L]] + 1) * a[L];sum[a[L]]++;}//若当前 R 在 r 的左边while (R < r) {R++;now += (2 * sum[a[R]] + 1) * a[R];sum[a[R]]++;}//若当前 R 在 r 的右边while (R > r) {now -= (2 * sum[a[R]] - 1) * a[R];sum[a[R]]--;R--;}
}
int main() {scanf("%d%d", &N, &T);for (int i = 1; i <= N; i++) scanf("%I64d", &a[i]);for (int i = 1; i <= T; i++) {scanf("%d%d", &Q[i].l, &Q[i].r);Q[i].id = i;}block_size = sqrt(N);sort(Q + 1, Q + T + 1, cmp);for (int i = 1; i <= T; i++) {modify(Q[i].l, Q[i].r);ans[Q[i].id] = now;}for (int i = 1; i <= T; i++) {printf("%I64d\n", ans[i]);}return 0;
}

2.C. Gerald and Giant Chess

  • 题意:给出一个h*w的棋盘(h,w<=1e5),其中有n个位置不能走(n<=2000),现在要从左上角走到右下角,每步只能向下或者向右走一步。问有多少种走法?右下角保证可以走到。
  • Let’s denote black cells as A 0 , A 1 , . . . , A k − 1 A_0, A_1, ..., A_{k - 1} A0,A1,...,Ak1. First of all, we have to sort black cells in increasing order of (row, column). If cell x available from cell y, x stands after y in this order. Let Ak = (h, w). Now we have to find number of paths from (1, 1) to A k A_k Ak avoiding A 0 , . . . , A k − 1 A_0, ..., A_{k - 1} A0,...,Ak1.
  • 对于 A i A_i Ai,从 ( 1 , 1 ) (1, 1) (1,1) 走到 ( x i , y i ) (x_i, y_i) (xi,yi) 且绕开前方所有黑格子的方法数目为 d p ( i ) dp(i) dp(i), 那么找到所有能到 A i A_{i} Ai 的节点(比如 ( x j , y j ) (x_j, y_j) (xj,yj)),把这个 d p ( i ) dp(i) dp(i) 减掉 d p ( j ) dp(j) dp(j) 与 从 A j A_j Aj A j A_j Aj 的所有路径就好。设 d p ( i ) dp(i) dp(i) 为到达 A i A_i Ai 的不合法路径总数。
  • 最后那个乘积道的理很简单,因为后面的那个从 A j A_j Aj A i A_i Ai 的全部路径,乘上躲开前方所有黑色节点的路径,其实已经是经过 A i A_i Ai 的不经过前方所有黑色节点的不合法路径了,这样就把所有不合法路径不重不漏的找到了。
  • 注意,前k个节点,是寻找的走到黑格子的方案数,所以是不合法路径的数目。但是最后一个格子是目标点,此时的 dp(i) 是合法路径的数量呀。
  • 最后发现傻掉了。数组要开到200010,因为会计算 C ( N + M − 2 , N − 1 ) C(N + M - 2, N - 1) C(N+M2,N1).
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
const int maxn = 2010;
ll dp[maxn], fact[200010] = { 1 }, infact[200010] = { 1 };
typedef pair<int, int> P;
vector<P> black;
ll N, M, T;
ll mod_pow(ll x, ll n) {ll res = 1;while (n) {if (n & 1) res = res * x % mod;x = x * x % mod;n >>= 1;}return res;
}
ll C(ll M, ll N) {return fact[M] * infact[N] % mod * infact[M - N] % mod;
}
int main() {cin >> N >> M >> T;for (ll i = 1; i <= 200000; i++) {fact[i] = fact[i - 1] * i % mod;infact[i] = infact[i - 1] * mod_pow(i, mod - 2) % mod;}for (int i = 0; i < T; i++) {int x, y;cin >> x >> y;black.push_back(P(x, y));}black.push_back(P(N, M));sort(black.begin(), black.end());for (int i = 0; i < black.size(); i++) {int xi = black[i].first, yi = black[i].second;dp[i] = C((ll)xi + yi - 2, (ll)xi - 1);for (int j = 0; j < i; j++) {int xj = black[j].first, yj = black[j].second;if (yj > yi) continue;dp[i] = (dp[i] - dp[j] * C((ll)xi + yi - xj - yj, (ll)xi - xj) % mod + mod) % mod;}}cout << dp[T] << endl;return 0;
}

3.F. Kate and imperfection

  • 题意:在1~n的n个数中,对于k∈[2,n],在n个数中取k个数,对这k个数两两进行gcd,输出这个gcd最小的最大值。
  • 思路:因为要求gcd,我们肯定要先把素数(还有1)一个一个放进去。当素数放完之后,我们接下来放的数字一定会是当前集合某个数的倍数。那么我们要让这个数字尽可能地小。于是,我们按照最大非自身地因子尽可能地小,往里面填放,就可以得到最优解。因此,把最大公因子从小到大排序,然后输出第2~第n小的数字即可。
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 500010;
int prime[maxn], st[maxn], cnt;
void sieve(int N) {st[1] = 1;for (int i = 2; i <= N; i++) {if (!st[i]) prime[cnt++] = st[i] = i;for (int j = 0; prime[j] <= N / i; j++) {st[prime[j] * i] = prime[j];if (i % prime[j] == 0) break;}}
}
int main() {sieve(maxn - 1);int N;scanf("%d", &N);for (int i = 1; i <= N; i++) {st[i] = i / st[i];}sort(st + 1, st + N + 1);for (int i = 2; i <= N; i++) {printf("%d%c", st[i], i == N ? '\n' : ' ');}return 0;
}

4.E. Magic Stones

  • 给出a数组,b数组,下标为2~n-1 可以进行操作,a[i]=a[i+1]+a[i-1]-a[i],问进行若干次操作,能否将a数组变成b数组。
  • Consider the difference array d 1 , d 2 , … , d n − 1 , d_1,d_2,…,d_{n−1}, d1,d2,,dn1, where d i = c i + 1 − c i . d_i=c_{i+1}−c_i. di=ci+1ci. Let’s see what happens if we apply synchronization.
  • Pick an arbitrary index j j j and transform c j c_j cj to c j ′ = c j + 1 + c j − 1 − c j . c^′_j=c_{j+1}+c_{j−1}−c_j. cj=cj+1+cj1cj. Then:
  • d j − 1 ′ = c j ′ − c j − 1 = ( c j + 1 + c j − 1 − c j ) − c j − 1 = c j + 1 − c j = d j d^′_{j−1}=c^′_j−c_{j−1}=(c_{j+1}+c_{j−1}−c_j)−c_{j−1}=c_{j+1}−c_j=d_j dj1=cjcj1=(cj+1+cj1cj)cj1=cj+1cj=dj
  • d j ′ = c j + 1 − c j ′ = c j + 1 − ( c j + 1 + c j − 1 − c j ) = c j − c j − 1 = d j − 1 d^′_j=c_{j+1}−c^′_j=c_{j+1}−(c_{j+1}+c_{j−1}−c_j)=c_j−c_{j−1}=d_{j−1} dj=cj+1cj=cj+1(cj+1+cj1cj)=cjcj1=dj1
  • 新奇,居然就是差分数组的交换。看看两个差分数组的前n-1项是否是一样的map就行。
  • 如果两个数组的差分数组一样,那么这两个数组就是一样的(因为前缀和一样啊)。
#include<cstdio>
#include<algorithm>
#include<unordered_map>
using namespace std;
const int maxn = 100010;
typedef long long ll;
ll c[maxn], t[maxn], c_d[maxn], t_d[maxn];
unordered_map<ll, ll> c_map, t_map;
int N;
int main() {scanf("%d", &N);for (int i = 1; i <= N; i++) scanf("%lld", &c[i]);for (int i = 1; i <= N; i++) scanf("%lld", &t[i]);for (int i = 1; i <= N; i++) c_d[i] = c[i] - c[i - 1], t_d[i] = t[i] - t[i - 1];for (int i = 1; i <= N; i++) c_map[c_d[i]]++, t_map[t_d[i]]++;bool flag = (c[1] == t[1] && c[N] == t[N]);for (auto p : c_map) {int u = p.first, cnt = p.second;if (t_map[u] != cnt) {flag = false;break;}}if (flag) printf("Yes\n");else printf("No\n");return 0;
}

5.D. Almost Difference

  • 很简单的一道题,不多说了,就是会爆 long long。网上有人使用long double写的。高精度写起来的好麻烦的,还要判断正负号。所以还是用 long double 吧。
  • long double 用 %.Lf 输出。注意L要大写。
#include<cstdio>
#include<algorithm>
#include<unordered_map>
#include<vector>
using namespace std;
const int maxn = 200010;
int N;
typedef long long ll;
ll a[maxn];
unordered_map<ll, ll> a_cnt;int main() {scanf("%d", &N);for (int i = 1; i <= N; i++) scanf("%lld", &a[i]);long double ans = 0;for (int i = 1; i <= N; i++) {ans += (ll)i * a[i] -((ll)N - (ll)i + 1LL) * a[i];}for (int i = 1; i <= N; i++) {a_cnt[a[i]]++;ll x = a_cnt[a[i] + 1], y = a_cnt[a[i] - 1];ans = ans + x - y;}printf("%.Lf\n", ans);return 0;
}

2300

1.D. The Child and Sequence

  • 题目大意:
    给你一个序列,你要在这个序列上进行操作。
    操作1 : 给定区间 [ l , r ] [ l , r ] [l,r],对序列中这个区间的所有数求和。
    操作2 : 给你区间 [ l , r ] [ l , r ] [l,r] 和 x,该区间所有数对 x 取模。
    操作3 :给你两个数 k k k x x x,把 a [ k ] a[k] a[k] 修改为 x x x
  • 1和3都是常规操作,2的话,其实只需要把当前区间的最大值m修改为 m % x,这个修改也很简单,当递归到某个区间长度为1的时候,把这个数字修改掉,然后向上pushup即可。
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 100010;
typedef long long ll;
int N, M, a[maxn];
struct node {int l, r;ll sum, m;
}tr[maxn * 4];
void pushup(node& u, node& l, node& r) {u.sum = l.sum + r.sum;u.m = max(l.m, r.m);
}
void pushup(int u) {pushup(tr[u], tr[2 * u], tr[2 * u + 1]);
}
void pushdown(int u) {auto& root = tr[u], & left = tr[2 * u], & right = tr[2 * u + 1];}
void build(int u, int l, int r) {if (l == r) tr[u] = { l, r, a[l], a[l] };else {tr[u].l = l, tr[u].r = r;int mid = (l + r) / 2;build(2 * u, l, mid), build(2 * u + 1, mid + 1, r);pushup(u);}
}
node query(int u, int l, int r) {if (l <= tr[u].l && tr[u].r <= r) return tr[u];int mid = (tr[u].l + tr[u].r) / 2;if (r <= mid) return query(2 * u, l, r);else if (l > mid) return query(2 * u + 1, l, r);else {node left = query(2 * u, l, r);node right = query(2 * u + 1, l, r);node res;pushup(res, left, right);return res;}
}
void modify(int u, int x, ll d) {if (tr[u].l == x && tr[u].r == x) tr[u] = { x, x, d, d};else {int mid = (tr[u].l + tr[u].r) / 2;if (x <= mid) modify(2 * u, x, d);else modify(2 * u + 1, x, d);pushup(u);}
}
void modify(int u, int l, int r, int x) {if (tr[u].m < x) return;if (tr[u].l == tr[u].r && tr[u].l >= l && tr[u].r <= r) {tr[u].m %= x;tr[u].sum = tr[u].m;}else {int mid = (tr[u].l + tr[u].r) / 2;if (l <= mid) modify(2 * u, l, r, x); //进入左区间if (r > mid) modify(2 * u + 1, l, r, x);  //进入右区间//else modify(2 * u, l, r, x), modify(2 * u + 1, l, r, x);  //进入两个区间pushup(u);}
}
int main() {scanf("%d%d", &N, &M);for (int i = 1; i <= N; i++) scanf("%d", &a[i]);build(1, 1, N);while (M--) {int op, l, r, x, k;scanf("%d", &op);if (op == 1) {scanf("%d%d", &l, &r);printf("%lld\n", query(1, l, r).sum);}else if (op == 2) {scanf("%d%d%d", &l, &r, &x);modify(1, l, r, x);}else {scanf("%d%d", &k, &x);modify(1, k, x);}}return 0;
}

2.B. Count Pairs

  • 题意:You are given a prime number p , n p, n p,n integers a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,,an, and an integer k k k.Find the number of pairs of indexes ( i , j ) (i,j) (i,j) ( 1 ≤ i < j ≤ n ) (1≤i<j≤n) (1i<jn) for which ( a i + a j ) ( a i 2 + a j 2 ) ≡ k m o d p (a_i+a_j)(a^2_i+a^2_j)≡k\ mod\ p (ai+aj)(ai2+aj2)k mod p.
    在这里插入图片描述
  • nm,这谁能想得到。不过,我感觉,碰到这么稀奇古怪的等式,很多时候都是想办法把 i i i j j j 弄到等号两边去。
#include<cstdio>
#include<algorithm>
#include<unordered_map>
using namespace std;
typedef long long ll;
const int maxn = 300010;
ll N, mod, K;
unordered_map<ll, ll> cnt;
ll mod_pow(ll x, ll n) {ll res = 1;while (n) {if (n & 1) res = res * x % mod;x = x * x % mod;n >>= 1;}return res;
}
int main() {scanf("%lld%lld%lld", &N, &mod, &K);for (int i = 0; i < N; i++) {ll x;scanf("%lld", &x);x = ((mod_pow(x, 4) - x * K) % mod + mod) % mod;cnt[x]++;}ll ans = 0;for (auto p : cnt) {int u = p.second;ans += u * (u - 1) / 2;}printf("%lld\n", ans);return 0;
}

3.C. Sasha and Array

  • 题意很简单,给一个数组
    在这里插入图片描述
  • 线段树是这样存储的:叶子节点存储 { f i b ( a i ) , f i b ( a i + 1 ) } \{fib(a_i), fib(a_i+1)\} {fib(ai),fib(ai+1)}. 就是一个 f f f 数组,那么非叶节点 f f f 数组的两个分别存储两个儿子的 f f f 的值之和。
  • 那么,懒人标记怎么办呢?其实就是储存一个矩阵 Q x Q^{x} Qx,因为,直接用 f f f 去乘 Q x Q^x Qx,就等价于把每个节点都加了一个数字,然后加起来。
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 100010;
typedef long long ll;
const ll mod = 1e9 + 7;
ll a[maxn];
int N, M;
const ll E[2][2] = { {1, 0}, {0, 1} };
struct node {int l, r;ll f[2], mul[2][2];
}tr[maxn * 4];
void mul(ll c[], ll a[], ll b[][2]) {ll tmp[2] = { 0 };for (int i = 0; i < 2; i++) {for (int j = 0; j < 2; j++) {tmp[i] = (tmp[i] + a[j] * b[j][i]) % mod;}}memcpy(c, tmp, sizeof tmp);
}
void mul(ll c[][2], ll a[][2], ll b[][2]) {ll tmp[2][2] = { 0 };for (int i = 0; i < 2; i++) {for (int j = 0; j < 2; j++) {for (int k = 0; k < 2; k++) {tmp[i][j] = (tmp[i][j] + a[i][k] * b[k][j]) % mod;}}}memcpy(c, tmp, sizeof tmp);
}
ll fib(ll n) {ll f[2] = { 1, 1 };ll a[2][2] = {{0, 1},{1, 1}};n -= 2;/*if (n == 0) return 1;*/while (n > 0) {if (n & 1) mul(f, f, a);mul(a, a, a);n >>= 1;}return f[1];
}
void pushup(node& u, node& l, node& r) {u.f[0] = (l.f[0] + r.f[0]) % mod;u.f[1] = (l.f[1] + r.f[1]) % mod;
}
void pushup(int u) {pushup(tr[u], tr[2 * u], tr[2 * u + 1]);
}void build(int u, int l, int r) {memcpy(tr[u].mul, E, sizeof E);if (l == r) {tr[u] = { l, l, fib(a[l]), fib(a[l] + 1) };}else {tr[u].l = l, tr[u].r = r;int mid = (l + r) / 2;build(2 * u, l, mid), build(2 * u + 1, mid + 1, r);pushup(u);}
}
void eval(node& u, ll q[][2]) {mul(u.f, u.f, q);mul(u.mul, u.mul, q);
}
void pushdown(int u) {eval(tr[2 * u], tr[u].mul);eval(tr[2 * u + 1], tr[u].mul);memcpy(tr[u].mul, E, sizeof E);
}
node query(int u, int l, int r) {if (l <= tr[u].l && tr[u].r <= r) return tr[u];pushdown(u);int mid = (tr[u].l + tr[u].r) / 2;if (r <= mid) return query(2 * u, l, r);else if (l > mid) return query(2 * u + 1, l, r);else {node left = query(2 * u, l, r);node right = query(2 * u + 1, l, r);node res;pushup(res, left, right);return res;}
}
void modify(int u, int l, int r, ll q[][2]) {if (l <= tr[u].l && tr[u].r <= r) {eval(tr[u], q);}else {pushdown(u);int mid = (tr[u].l + tr[u].r) / 2;if (l <= mid) modify(2 * u, l, r, q);if (r > mid) modify(2 * u + 1, l, r, q);pushup(u);}
}
int main() {//for (int i = 1; i <= 10; i++) printf("*** %lld\n", fib(i));scanf("%d%d", &N, &M);for (int i = 1; i <= N; i++) scanf("%lld", &a[i]);build(1, 1, N);while (M--) {int t, l, r;ll x;scanf("%d%d%d", &t, &l, &r);if (t == 1) {scanf("%lld", &x);ll res[2][2];memcpy(res, E, sizeof E);ll multi[2][2]{{0, 1},{1, 1}};while (x) {if (x & 1) mul(res, res, multi);mul(multi, multi, multi);x >>= 1;}modify(1, l, r, res);}else {printf("%lld\n", query(1, l, r).f[0]);}}return 0;
}

4.C. Mike and Foam

  • 题意:n个数,q个查询操作,每次操作对第x个数进行操作,如果序列里面没有这个数,就把这个数加进去,如果有就取出来,问在序列中有多少组数,满足互质的情况。
  • 其实就是一个简单的容斥原理,但是要小心1,这道题我在这个地方绊了好几下。
#include<cstdio>
#include<algorithm>
#include<unordered_map>
#include<vector>
#include<unordered_set>
using namespace std;
const int maxa = 500010, maxn = 200010;
int N, Q, a[maxn];
int prime[maxa], cnt, st[maxa];
void sieve(int N) {st[1] = 1;for (int i = 2; i <= N; i++) {if (!st[i]) st[i] = prime[cnt++] = i;for (int j = 0; prime[j] <= N / i; j++) {st[prime[j] * i] = prime[j];if (i % prime[j] == 0) break;}}
}
typedef unordered_map<int, int> um;
um now;
unordered_set<int> ss;
int divisor(int N, bool flag) {int n = N;vector<int> res;int ans = ss.size();while (N > 1) {int x = st[N];res.push_back(x);while (N % x == 0) N /= x;}int sz = res.size();for (int i = 1; i < (1 << sz); i++) {int xx = 1, cnt = 0;for (int j = 0; j < sz; j++) {if ((i >> j) & 1) {xx *= res[j];cnt++;}}if (cnt & 1) ans -= now[xx];else ans += now[xx];if (flag) now[xx]--;else now[xx]++;}if (n == 1 && flag) return -ans + 1;else if (flag) return -ans;return ans;
}typedef long long ll;int main() {scanf("%d%d", &N, &Q);for (int i = 1; i <= N; i++) scanf("%d", &a[i]);sieve(maxa - 1);ll ans = 0;while (Q--) {int x;scanf("%d", &x);ans += divisor(a[x], ss.count(x));if (ss.count(x)) ss.erase(x);else ss.insert(x);printf("%lld\n", ans);}return 0;
}

5.E2. Asterism (Hard Version)

  • 题意:给出n个敌人,每个敌人有 a i a_i ai 个糖果,你一开始手中有 x x x 个糖果,每次只能选择糖果数小于你当前手中糖果的敌人,战胜他并获得一颗糖果。选择敌人的顺序构成一个排列P。 f ( x ) f(x) f(x) 表示一开始有 x x x 个糖果时,可行的排列方案数。给出一个素数 p ( p ⩽ n ) p(p\leqslant n) p(pn),求出所有满足 f ( x ) m o d p ≠ 0 f ( x )\ mod\ p \ne 0 f(x) mod p=0 的 x.
  • 算了,这道题好恶心呀,不想写了。

2400

1.C. DZY Loves Fibonacci Numbers

  • 题意:给出一个数列,每次可以选取一个区间,按顺序加上第i个菲波那契数进行更新,也可以查询某一个区间的总和。
  • 斐波那契数列性质:
  1. 若一个数列满足F(1)=a,F(2)=b,F(i)=F(i-2)+F(i-1),则有F(n)=aFib(n-2)+bFib(n-1),且F的前N项和等于F(n+2)-F(2)。这个很容易推导出来。
  2. 两个菲波那契数列相加之后依旧是一个菲波那契数列,只是前两项的值变化,分别变为了两个菲波那契数列前两项的和。
  • 因此,这样的类斐波那契数列,想求 F ( n ) F(n) F(n) 的前n项和,只需要求 F ( n + 2 ) − F ( 2 ) F(n+2) - F(2) F(n+2)F(2)
    即可。而 F ( n ) = a ∗ F ( n − 2 ) + b ∗ F ( n − 1 ) . F(n) = a * F(n-2) + b * F(n-1). F(n)=aF(n2)+bF(n1). a a a b b b 分别为这个类斐波那契数列的前两项。
  • 这里的懒人标记是f1, f2,表示在此节点的整个区间上面加上一个前两项是 f 1 f1 f1 f 2 f2 f2 的类斐波那契数列。
  • 不过,我要先说一下懒人标记,这个东西别用错了。这道题在懒人标记上面错了很长时间。懒人标记是方便你往下pushdown的时候用的,有这些特点:
  1. 每一次pushdown的时候,线段树节点的某些属性是要清零的。
  2. 懒人标记中被清零的那些属性一般来讲与答案不会直接相关。只是为了方便修改某个与答案直接相关的属性而存在的(比如sum),而这个属性与答案是直接相关的。
  3. 懒人标记是为了修改两个子节点的整个区间的,就比如这个题,f1, f2 是为了在两个子区间上面直接加上一个类斐波那契数列的一段,而这个类斐波那契数列的区间长度和对应子区间的长度是一致的。

最后,我想说,线段树的题目真的难调!主要还是思路上面不是很完整,有漏洞。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 300010;
typedef long long ll;
const ll mod = 1e9 + 9;
ll fib[maxn] = { 1, 1, 1 };
ll a[maxn];
struct node {int l, r;ll sum, f1, f2;
}tr[maxn * 4];
void pushup(node& u, node& l, node& r) {u.sum = (l.sum + r.sum) % mod;
}
void pushup(int u) {pushup(tr[u], tr[2 * u], tr[2 * u + 1]);
}
ll getFib(int n, ll a, ll b) {if (n == 1) return a % mod;if (n == 2) return b % mod;return (a % mod * fib[n - 2] % mod + b % mod * fib[n - 1] % mod) % mod;
}
ll sum(int n, ll f1, ll f2) {return ((getFib(n + 2, f1, f2) - f2) % mod + mod) % mod;
}
//void eval(int u) {
//	tr[u].sum = sum(tr[u].r - tr[u].l + 1, tr[u].f1, tr[u].f2);
//}
void pushdown(int u) {ll f1 = tr[u].f1, f2 = tr[u].f2;node& root = tr[u], & left = tr[2 * u], & right = tr[2 * u + 1];if (f1 == 0) return;left.f1 = (left.f1 + f1) % mod, left.f2 = (left.f2 + f2) % mod;right.f1 = (right.f1 + getFib(right.l - root.l + 1, f1, f2)) % mod;right.f2 = (right.f2 + getFib(right.l - root.l + 2, f1, f2)) % mod;tr[2 * u].sum += sum(left.r - left.l + 1, f1, f2);tr[2 * u].sum %= mod;ll x = getFib(right.l - root.l + 1, f1, f2);ll y = getFib(right.l - root.l + 2, f1, f2);tr[2 * u + 1].sum += sum(right.r - right.l + 1, x, y);tr[2 * u + 1].sum %= mod;tr[u].f1 = tr[u].f2 = 0;
}
void build(int u, int l, int r) {if (l == r) tr[u] = { l, r, 0, 0, 0 };else {tr[u] = { l, r, 0, 0, 0 };int mid = (l + r) / 2;build(2 * u, l, mid), build(2 * u + 1, mid + 1, r);pushup(u);}
}
void modify(int u, int l, int r) {if (l <= tr[u].l && tr[u].r <= r) {//printf("$$$ %d %d %lld %lld %lld\n", tr[u].l, tr[u].r, tr[u].f1, tr[u].f2, tr[u].sum);tr[u].f1 = (tr[u].f1 + fib[tr[u].l - l + 1]) % mod;tr[u].f2 = (tr[u].f2 + fib[tr[u].l - l + 2]) % mod;tr[u].sum += sum(tr[u].r - tr[u].l + 1, fib[tr[u].l - l + 1], fib[tr[u].l - l + 2]);tr[u].sum %= mod;//printf("### %d %d %lld %lld %lld\n", tr[u].l, tr[u].r, tr[u].f1, tr[u].f2, tr[u].sum);}else {pushdown(u);int mid = (tr[u].l + tr[u].r) / 2;if (l <= mid) modify(2 * u, l, r);if (r > mid) modify(2 * u + 1, l, r);pushup(u);}
}
node query(int u, int l, int r) {if (l <= tr[u].l && tr[u].r <= r) return tr[u];pushdown(u);int mid = (tr[u].l + tr[u].r) / 2;if (r <= mid) return query(2 * u, l, r);else if (l > mid) return query(2 * u + 1, l, r);else {node left = query(2 * u, l, r);node right = query(2 * u + 1, l, r);node res;pushup(res, left, right);return res;}
}
int main() {for (int i = 3; i < maxn; i++) fib[i] = (fib[i - 1] + fib[i - 2]) % mod;int N, M;scanf("%d%d", &N, &M);build(1, 1, N);for (int i = 1; i <= N; i++) scanf("%lld", &a[i]);for (int i = 1; i <= N; i++) a[i] = (a[i] + a[i - 1]) % mod;//printf("&$ %lld\n", a[N]);while (M--) {int t, l, r;scanf("%d%d%d", &t, &l, &r);if (t == 1) {modify(1, l, r);}else {ll ans = (((query(1, l, r).sum + a[r] - a[l - 1]) % mod) + mod) % mod;printf("%lld\n", ans);}//for (int i = 1; i <= N; i++) {//	ll ans = query(1, i, i).sum + a[i] - a[i - 1];//	printf("*** %lld %lld %lld %lld\n", ans, query(1, i, i).f1, query(1, i, i).f2, query(1, i, i).sum);//}//printf("\n");}return 0;
}

2.B. Triangles and a Circle

  • 题意:给你一个圆,圆上有很多点,问这些点组成的三角形有多少个是包含了圆心的(圆心在三角形边上也算)。
  • 思路:可以算一个点往后半个圆周内有多少个点,然后把这些点组成的三角形减掉。
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;const int maxn = 300010;
ll a[maxn];
int N, L;
int main(){scanf("%d%d", &N, &L);for (int i = 0; i < N; i++)scanf("%lld", &a[i]);sort(a, a + N);ll ans = (ll)N * ((ll)N - 1LL) * ((ll)N - 2LL) / 6LL;for (int i = 0; i < N; i++){int u = a[i] + L / 2;ll cnt;if (u < L) {int lb = 0, ub = N;while (ub - lb > 1) {int mid = (ub + lb) / 2;if (a[mid] <= u) lb = mid;else ub = mid;}if ((a[lb] - a[i]) * 2 == L) lb--;//printf("*** %d\n", lb);cnt = lb - i;//printf("### %d\n", cnt);}else {u %= L;int lb = 0, ub = N;while (ub - lb > 1) {int mid = (ub + lb) / 2;if (a[mid] <= u) lb = mid;else ub = mid;}if ((a[i] - a[lb]) * 2 == L) lb--;//printf("*** %d\n", lb);cnt = N - (i - lb);if (u < a[0]) cnt--;}ans -= (cnt - 1) * cnt / 2;}printf("%lld\n", ans);return 0;
}
/*
4 10
0 2 3 44 10 
0 2 3 53 10
0 5 63 7
0 4 5*/

http://www.ppmy.cn/news/622033.html

相关文章

CF刷题(01)——难度1600

从今天起&#xff0c;cf开刷&#xff0c;先从难度1600写起试试&#xff0c;每个难度的题目放在一个博客里面。看看最后可以写多少个博客。每一道AC的题目的代码都放在这里 &#x1f603; 1600打算写25道题后晋级1700. 最近训练重心&#xff1a;数学 math、number theory、pro…

cf-#163-总结

郁闷啊&#xff0c;本来能交第三题的。。。 自己的思维能力还是有缺陷&#xff0c;打代码的能力不行。 虽然打字速度还凑合着&#xff0c;但是没有大局观。 必须提高由思想转化成代码的速度。 做法就是多打代码~~~~

寒假cf补题

C. Monsters And Spells 怪兽会在第K秒出现&#xff0c;血量是H&#xff0c;任务是在它出现的时候消灭它。上一秒如果没有使用过魔法&#xff0c;那么这一秒只能施展1的魔法&#xff1b;如果上一秒使用过x的魔法&#xff0c;这一秒可以使用x1的魔法。若魔法x>H&#xff0c;…

CF刷题记录

dp&#xff08;3&#xff09;&#xff1a; 1、1582F1&#xff0c;a[i]的值范围很小&#xff0c;因此我们可以暴力枚举x&#xff0c;使满足条件时令dp[x^a[i]]1&#xff0c;那么怎么满足异或出的数列是递增的呢&#xff0c;我们用f[x]记录异或值为x时数列中的最大值&#xff0c;…

【CF/其他 经验总结贴】KeyMind篇(一)

【CF/其他 经验总结贴】Key&Mind篇&#xff08;一&#xff09; 食用说明 \&#xff08;‘ - ‘&#xff09;/ 个人训练总结用&#xff0c;主要是关键词联想丰富自己脑洞 Key&Mind 关键词联想小于x的最大可用二分&#xff0c;set配对分组统计每组数量&#xff1b;小…

【解题报告】CF练一下题 | 难度CF2500左右

【解题报告】CF练一下题 | 难度CF2500左右 Ciel and Gondolas | CF321E题意思路 | dp | 决策单调性 | 二维前缀和代码 Least Cost Bracket Sequence | CF3D题意思路 | 贪心代码 Buy Low Sell High | CF865D题意思路 | 贪心 | 可反悔贪心代码 Nearest Leaf | CF1110F题意思路 | …

CF小组训练赛 Holiday 19

A. Balls of Buma 具体题目地址可以直接搜索题目标题 题目大意 "BBWWBB"代表不同颜色的球&#xff0c;要求往这个不同颜色的球构成的球段的某位置放入一个某种颜色的球&#xff0c;如果放入球后&#xff0c;如果某些相同颜色的球段由于上一个动作而变长&#xff0…

2023-06-25:redis中什么是缓存穿透?该如何解决?

2023-06-25&#xff1a;redis中什么是缓存穿透&#xff1f;该如何解决&#xff1f; 答案2023-06-25&#xff1a; 缓存穿透 缓存穿透指的是查询一个根本不存在的数据&#xff0c;在这种情况下&#xff0c;无论是缓存层还是存储层都无法命中。因此&#xff0c;每次请求都需要访…