文章目录
A: 带宽 25
B: 纯质数 1903
考场上用的欧拉筛,这里用埃氏筛。
#include <bits/stdc++.h>
using namespace std;
const int N = 20210606;
bool vis[N];
vector<int> primes;
void init()
{
int i;
for (i = 2; i < N; ++i) {
if (!vis[i]) primes.push_back(i);
for (int j = 0; i*primes[j] < N; ++j) {
vis[i*primes[j]] = 1;
if (i%primes[j] == 0) break;
}
}
}
int main()
{
init();
int ans = primes.size();
for (auto x : primes) {
while (x) {
int t = x%10;
if (t != 2 && t != 3 && t != 5 && t != 7) {
--ans; break;
}
x /= 10;
}
}
cout << ans; // 1903
return 0;
}
C: 完全日期 977
不熟悉 p y t h o n python python 的日期类,怕用错,于是手动模拟日期变化。
#include <bits/stdc++.h>
using namespace std;
class Date
{
int y, m, d;
public:
Date(int y, int m, int d) : y(y), m(m), d(d) {}
void add() {
int nums[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
if (y%400 == 0 || y%100 && y%4 == 0) ++nums[2];
if (++d > nums[m]) {
d = 1;
if (++m > 12) m = 1, ++y;
}
}
bool operator == (const Date &t) const {
return y == t.y && m == t.m && d == t.d;
}
bool valid() {
auto sum = [] (int x) {
int ret = 0;
while (x) ret += x%10, x /= 10;
return ret;
};
int t = sum(y)+sum(m)+sum(d);
int i = 0;
while (i*i < t) ++i;
return i*i == t;
}
};
int main()
{
Date s(2001, 1, 1), t(2022, 1, 1);
int ans = 0;
while (!(s == t)) {
ans += s.valid();
s.add();
}
cout << ans; // 977
return 0;
}
D: 最小权值 2653631372
题目直接给出状态转移方程,感觉 d p dp dp 的暗示还是蛮明显的。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL dp[2022];
int main()
{
for (int i = 1; i <= 2021; ++i) {
dp[i] = LLONG_MAX;
for (int j = 0; j < i; ++j) {
dp[i] = min(dp[i], 1+2*dp[j]+3*dp[i-1-j]+j*j*(i-1-j));
}
// cout << dp[i] << ' ';
}
cout << dp[2021] << endl; // 2653631372
return 0;
}
E: 大写
国赛遇上这种题应该挺难得吧。
#include <bits/stdc++.h>
using namespace std;
int main()
{
char c;
while (cin >> c) cout << char((c | 0x20) ^ 0x20);
return 0;
}
F: 123
这题公式其实蛮好推的。
只要知道
∑
i
=
1
n
i
=
n
(
n
+
1
)
2
,
∑
i
=
1
n
i
2
=
n
(
n
+
1
)
(
2
n
+
1
)
6
\sum_{i=1}^n i=\frac{n(n+1)}{2},\ \sum_{i=1}^n i^2=\frac{n(n+1)(2n+1)}{6}
∑i=1ni=2n(n+1), ∑i=1ni2=6n(n+1)(2n+1) 即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL calc(LL j) { return (j*(j+1)*(2*j+1)/6 + j*(j+1)/2)/2; }
LL solve(LL n) // 计算 1...n 项和
{
LL j = (sqrt(8*n+1)-1)/2;
LL res = n - j*(j+1)/2;
assert(res <= j);
return calc(j) + res*(res+1)/2;
}
int main()
{
LL T;
cin >> T;
while (T--) {
LL l, r;
cin >> l >> r;
cout << solve(r) - solve(l-1) << endl;
}
return 0;
}
/*
3
1 1
1 3
5 8
*/
G: 异或变换(已补)
考场上没找到规律,只做了 40% 的子任务。
01 01 01 串 s = s 1 s 2 s 3 ⋯ s n s=s_1s_2s_3\cdots s_n s=s1s2s3⋯sn .
在左边添加无数个 0 0 0 将其扩充为 ⋯ s − 1 s 0 s 1 s 2 ⋯ s n \cdots s_{-1}s_0s_1s_2\cdots s_n ⋯s−1s0s1s2⋯sn ,其中 s 0 = s − 1 = ⋯ = s − ∞ = 0 s_0=s_{-1}=\cdots =s_{-\infty}=0 s0=s−1=⋯=s−∞=0 ,容易证明 ∀ i ∈ ( − ∞ , n ] , s i ′ = s i ⊕ s i − 1 \forall i\in (-\infty,n],s_i' = s_i\oplus s_{i-1} ∀i∈(−∞,n],si′=si⊕si−1 .
为方便讨论,将 s s s 进行 t t t 次异或变换后的字符串记为 s ( t ) s^{(t)} s(t) .
则有公式
s i ( t ) = s i ( t − 1 ) ⊕ s i − 1 ( t − 1 ) = ( s i ( t − 2 ) ⊕ s i − 1 ( t − 2 ) ) ⊕ ( s i − 1 ( t − 2 ) ⊕ s i − 2 ( t − 2 ) ) = s i ( t − 2 ) ⊕ s i − 2 ( t − 2 ) = ( s i ( t − 4 ) ⊕ s i − 2 ( t − 4 ) ) ⊕ ( s i − 2 ( t − 4 ) ⊕ s i − 4 ( t − 4 ) ) = s i ( t − 4 ) ⊕ s i − 4 ( t − 4 ) = ⋯ \begin{aligned} s^{(t)}_i&\ {\color{red}=s^{(t-1)}_i\oplus s^{(t-1)}_{i-1}}\\ &=(s^{(t-2)}_i\oplus s^{(t-2)}_{i-1})\oplus(s^{(t-2)}_{i-1}\oplus s^{(t-2)}_{i-2})\ {\color{red}=s^{(t-2)}_i\oplus s^{(t-2)}_{i-2}}\\ &=(s^{(t-4)}_i\oplus s^{(t-4)}_{i-2})\oplus(s^{(t-4)}_{i-2}\oplus s^{(t-4)}_{i-4})\ {\color{red}=s^{(t-4)}_i\oplus s^{(t-4)}_{i-4}}\\ &=\cdots \end{aligned} si(t) =si(t−1)⊕si−1(t−1)=(si(t−2)⊕si−1(t−2))⊕(si−1(t−2)⊕si−2(t−2)) =si(t−2)⊕si−2(t−2)=(si(t−4)⊕si−2(t−4))⊕(si−2(t−4)⊕si−4(t−4)) =si(t−4)⊕si−4(t−4)=⋯
由数学归纳法知,若 k k k 为 2 2 2 的幂且 k < t k < t k<t ,
s i ( t ) = s i ( t − k ) ⊕ s i − k ( t − k ) . s^{(t)}_i\ {\color{red}=s^{(t-k)}_i\oplus s^{(t-k)}_{i-k}}. si(t) =si(t−k)⊕si−k(t−k).
因此不断进行 2 2 2 的幂次变换,直至达到 t t t 次变换,即得结果。时间复杂度 O ( n log t ) O(n\log t) O(nlogt) .
#include <iostream>
#include <string>
using namespace std;
int main()
{
int n;
long long t;
scanf("%d%lld", &n, &t);
string s(n, 0);
scanf("%s", &s[0]);
auto power_transform = [&] (long long k) {
for (int i = n - 1; i >= 0; --i) {
s[i] ^= (i - k >= 0 ? s[i - k] : '0') ^ '0';
}
};
long long k = 1;
while (t) {
if (t & 1) power_transform(k);
k <<= 1;
t >>= 1;
}
printf("%s", &s[0]);
return 0;
}
H: 二进制问题
这是很裸的数位 d p dp dp 吧。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL dp[100][2][100]; // 长度为i 首位为j 二进制中含k个1 dp代表满足条件的个数
void init()
{
dp[1][0][0] = dp[1][1][1] = 1;
for (int i = 2; i < 80; ++i) {
for (int k = 0; k <= i; ++k) {
dp[i][0][k] = dp[i-1][0][k] + dp[i-1][1][k];
if (k > 0) dp[i][1][k] = dp[i-1][0][k-1] + dp[i-1][1][k-1];
// cout << dp[i][0][k] << ',' << dp[i][1][k] << ' ';
}
// cout << endl;
}
}
int digit[100];
int len = 0;
LL calc(LL n, LL k) // 1..n中有多少个满足条件的数
{
LL t = n;
while (t) {
digit[++len] = t&1; t >>= 1;
}
LL ans = 0;
for (int i = len; i >= 1; --i) {
if (i == 1) {
ans += dp[i][ digit[i] ][k];
}
if (digit[i]) {
ans += dp[i][0][k];
--k;
}
}
return ans;
}
int main()
{
init();
LL n, k;
cin >> n >> k;
cout << calc(n, k);
}
I: 反转括号序列(已补)
很明显的线段树特征,不过考场上还是没能想到怎么写,只做了 20% 的子任务。
思路一 线段树
记左括号为
1
1
1 ,右括号为
−
1
-1
−1 ,可求得前缀和 sum
,需要维护的区间
[
L
,
R
]
[L,R]
[L,R] 内的信息:
lb
: 区间左括号数rb
: 区间右括号数mis
: 前缀和的区间最小值 min i = L R ( s u m i ) \min\limits_{i=L}^R(sum_i) i=LminR(sumi)mas
: 前缀和的区间最大值 max i = L R ( s u m i ) \max\limits_{i=L}^R(sum_i) i=LmaxR(sumi)mbs
: 区间之前的前缀和 s u m L − 1 sum_{L-1} sumL−1
两种操作:
- 对于翻转操作,翻转区间 [ x , y ] [x,y] [x,y] 等价于依次翻转区间 [ x , n ] [x, n] [x,n] 和 [ y + 1 , n ] [y+1,n] [y+1,n] .
- 对于查询操作可以进行两次二分,第一次二分找出最大的
r
r
r 使得区间
[
x
,
r
]
[x,r]
[x,r] 的
mis
不小于mbs
,第二次二分找出最大的 y y y 使得区间 [ y , r ] [y,r] [y,r] 的mis
等于mbs
.
时间复杂度 O ( n + m log 2 n ) O(n+m\log^2 n) O(n+mlog2n) .
#include <iostream>
#include <vector>
#include <string>
using namespace std;
const int inf = 1e9 + 7;
struct node {
int lb, rb, mis, mas, mbs;
bool lazy_rev;
int lazy_add;
node() : lb(0), rb(0)
, mis(inf), mas(-inf)
, lazy_rev(false), lazy_add(0) {}
};
using T = node;
class lazy_segtree {
int n;
vector<T> tree;
void op(const T& a, const T& b, T& c) {
c.lb = a.lb + b.lb;
c.rb = a.rb + b.rb;
c.mis = min(a.mis, b.mis);
c.mas = max(a.mas, b.mas);
c.mbs = a.mbs;
}
void push_up(int id) {
op(tree[id << 1], tree[id << 1 | 1], tree[id]);
}
void rev_add(T &t, bool rev, int val) {
if (rev) {
swap(t.lb, t.rb);
t.mis = 2 * t.mbs - t.mis;
t.mas = 2 * t.mbs - t.mas;
swap(t.mis, t.mas);
t.lazy_rev ^= rev;
}
if (val) {
t.mis += val;
t.mas += val;
t.mbs += val;
t.lazy_add += val;
}
}
void push_down(int L, int R, int id) {
if (L == R) return;
auto &lson = tree[id << 1];
auto &rson = tree[id << 1 | 1];
rev_add(lson, tree[id].lazy_rev, tree[id].lazy_add);
rev_add(rson, tree[id].lazy_rev, tree[id].lazy_add + tree[id].lazy_rev * 2 * (lson.lb - lson.rb));
tree[id].lazy_rev = false;
tree[id].lazy_add = 0;
}
void build(vector<T> &a, int L, int R, int id) {
if (L == R) {
tree[id] = a[L - 1];
return;
}
int m = L + R >> 1;
build(a, L, m, id << 1);
build(a, m + 1, R, id << 1 | 1);
push_up(id);
}
int rev(int x, int y, int val, int L, int R, int id) {
if (x <= L && R <= y) {
rev_add(tree[id], true, val);
return 2 * (tree[id].lb - tree[id].rb);
}
push_down(L, R, id);
int m = L + R >> 1;
int t = 0;
if (x <= m) t = rev(x, y, val, L, m, id << 1);
if (y > m) t += rev(x, y, val + t, m + 1, R, id << 1 | 1);
push_up(id);
return t;
}
T query(int x, int y, int L, int R, int id) {
push_down(L, R, id);
if (x <= L && R <= y) {
return tree[id];
}
int m = L + R >> 1;
T res;
if (x <= m) op(res, query(x, y, L, m, id << 1), res);
if (y > m) op(res, query(x, y, m + 1, R, id << 1 | 1), res);
return res;
}
public:
lazy_segtree(vector<T> &v) : n(v.size()), tree(n << 2) {
build(v, 1, n, 1);
}
void rev(int x, int y) {
rev(x, n, 0, 1, n, 1);
if (y + 1 <= n) rev(y + 1, n, 0, 1, n, 1);
}
int query(int l) {
T t = query(l ,l, 1, n, 1);
if (t.rb) return 0;
int mis = t.mis - 1;
int L = l, R = n;
while (L < R) {
int m = L + R + 1 >> 1;
if (query(l, m, 1, n, 1).mis >= mis) L = m;
else R = m - 1;
}
int r = R;
t = query(l ,r, 1, n, 1);
if (t.mis > mis) return 0;
L = l;
while (L < R) {
int m = L + R + 1 >> 1;
if (query(m, r, 1, n, 1).mis == mis) L = m;
else R = m - 1;
}
return L;
}
};
int main()
{
int n, m;
scanf("%d%d", &n, &m);
string s(n, 0);
scanf("%s", &s[0]);
vector<node> v(n);
int t = 0;
for (int i = 0; i < n; ++i) {
v[i].lb = (s[i] == '(');
v[i].rb = (s[i] == ')');
v[i].mbs = t;
t += v[i].lb - v[i].rb;
v[i].mis = v[i].mas = t;
}
lazy_segtree st(v);
while (m--) {
int t, x, y;
scanf("%d%d", &t, &x);
if (t == 1) {
scanf("%d", &y);
st.rev(x, y);
}
else {
printf("%d\n", st.query(x));
}
}
return 0;
}
思路二 分块
记录每一个块中翻转和不反转情况下多余的左括号与右括号的数目,以及翻转标志。时间复杂度 O ( m n ) O(m\sqrt n) O(mn) .
#include <iostream>
#include <vector>
#include <string>
#include <cmath>
using namespace std;
struct Block {
bool rev;
int nl[2], nr[2];
Block() : rev(false) {}
};
int main()
{
int n, m;
scanf("%d%d", &n, &m);
string s(n, 0);
scanf("%s", &s[0]);
int step = sqrt(n) + 1, sz = (n + step - 1) / step;
vector<Block> blocks(sz);
auto update = [&] (int i) {
blocks[i].nl[0] = 0;
blocks[i].nl[1] = 0;
blocks[i].nr[0] = 0;
blocks[i].nr[1] = 0;
int l = i * step, r = min(n, l + step);
for (int j = l; j < r; ++j) {
if (s[j] == '(') ++blocks[i].nl[0];
else if (blocks[i].nl[0]) --blocks[i].nl[0];
else ++blocks[i].nr[0];
}
for (int j = l; j < r; ++j) {
if (s[j] == ')') ++blocks[i].nl[1];
else if (blocks[i].nl[1]) --blocks[i].nl[1];
else ++blocks[i].nr[1];
}
};
for (int i = 0; i < sz; ++i) {
update(i);
}
while (m--) {
int t, x, y;
scanf("%d%d", &t, &x);
--x;
if (t == 1) {
scanf("%d", &y);
--y;
int i = x / step, j = y / step;
int l = x, r = min(n, (i + 1) * step);
r = min(r, y + 1);
for (int j = l; j < r; ++j) {
s[j] = '(' - s[j] + ')';
}
update(i);
while (++i < j) {
blocks[i].rev ^= 1;
}
if (i == j) {
l = i * step, r = y + 1;
for (int j = l; j < r; ++j) {
s[j] = '(' - s[j] + ')';
}
update(i);
}
}
else {
int ans = 0;
int cnt = 0;
int i = x / step;
int l = x, r = min(n, (i + 1) * step);
for (int j = x; j < r; ++j) {
cnt += ((s[j] == '(') ^ (blocks[i].rev)) ? 1 : -1;
if (cnt < 0) break;
if (cnt == 0) ans = j + 1;
}
if (cnt >= 0) {
int t = i;
int p = i, pc;
while (++i < sz) {
if (blocks[i].nr[blocks[i].rev] < cnt) {
cnt += blocks[i].nl[blocks[i].rev] - blocks[i].nr[blocks[i].rev];
continue;
}
if (blocks[i].nr[blocks[i].rev] >= cnt) p = i, pc = cnt;
if (blocks[i].nr[blocks[i].rev] > cnt) break;
cnt += blocks[i].nl[blocks[i].rev] - blocks[i].nr[blocks[i].rev];
}
if (p > t) {
int l = p * step, r = min(n, l + step);
cnt = pc;
if (cnt == 0) ans = l;
for (int j = l; j < r; ++j) {
cnt += ((s[j] == '(') ^ (blocks[p].rev)) ? 1 : -1;
if (cnt < 0) break;
if (cnt == 0) ans = j + 1;
}
}
}
printf("%d\n", ans);
}
}
return 0;
}
J: 异或三角形(已补)
考场上没想到思路,只做了 20% 的子任务。
由题意知
- a ⊕ b ⊕ c = 0 ⇔ c = a ⊕ b a\oplus b\oplus c = 0 \Leftrightarrow c = a\oplus b a⊕b⊕c=0⇔c=a⊕b ;
- a , b , c a,b,c a,b,c 能构成三角形 ⇔ ∣ a − b ∣ < c < a + b \Leftrightarrow |a-b|\lt c \lt a+b ⇔∣a−b∣<c<a+b .
又因为当 a > b a > b a>b 时有
- a − b ≤ a ⊕ b ≤ a + b a-b \le a\oplus b \le a + b a−b≤a⊕b≤a+b .
- a − b = a ⊕ b a-b = a\oplus b a−b=a⊕b 当且仅当 a ∣ b = a a | b = a a∣b=a .
- a ⊕ b = ≤ a + b a\oplus b = \le a + b a⊕b=≤a+b 当且仅当 a & b = 0 a \& b = 0 a&b=0 .
记 $a=a_{29}a_{28}a_{27}\cdots a_1a_{0}, $ b = b 29 b 28 b 27 ⋯ b 1 b 0 b=b_{29}b_{28}b_{27}\cdots b_1b_{0} b=b29b28b27⋯b1b0 ,其中 a i , b i a_i,b_i ai,bi 分别代表 a , b a,b a,b 在第 i i i 位的二进制数.
故原题等价于求出有多少组 a , b a,b a,b 满足
- 1 ≤ a , b , a ⊕ b ≤ n 1 \le a,b,a\oplus b\le n 1≤a,b,a⊕b≤n ;
- ∃ i , ( a i , b i ) = ( 0 , 1 ) \exists i,(a_i,b_i)=(0,1) ∃i,(ai,bi)=(0,1) ;
- ∃ i , ( a i , b i ) = ( 1 , 1 ) \exists i,(a_i,b_i)=(1,1) ∃i,(ai,bi)=(1,1) ;
- ∃ i , ( a i , b i ) = ( 1 , 0 ) \exists i,(a_i,b_i)=(1,0) ∃i,(ai,bi)=(1,0) .
因此不妨设 a > b a > b a>b ,记忆化搜索求出数目后乘以 2 2 2 即得答案。
由于 dfs 时 i i i 的状态只从 i − 1 i-1 i−1 的状态转移过来,可将记忆化搜索计算数组 f f f 的过程视为动态规划。时间复杂度 O ( T log n ) O(T\log n) O(Tlogn) .
#include <iostream>
#include <cstring>
using namespace std;
long long f[2][1 << 3][30]; // f[cond][s][i] 代表 cond 约束下,当 a, b 的高位(比第 i 位更高的位)状态为 s 时合法的 (a,b) 对数目。
long long dfs(int i, int weight, int high_a, int high_b, int n, int s) {
int high_c = high_a ^ high_b;
if (high_a > n || high_b > high_a || high_c > n) return 0;
if (weight == 0) return s == 7;
int mask = weight * 2 - 1;
bool cond = high_a + mask <= n && high_c + mask <= n;
if (f[cond][s][i] != -1) return f[cond][s][i];
long long ans = 0;
ans += dfs(i - 1, weight >> 1, high_a, high_b, n, s);
ans += dfs(i - 1, weight >> 1, high_a, high_b + weight, n, s | 1);
ans += dfs(i - 1, weight >> 1, high_a + weight, high_b, n, s | 4);
ans += dfs(i - 1, weight >> 1, high_a + weight, high_b + weight, n, s | 2);
return f[cond][s][i] = ans;
}
int main()
{
int T;
scanf("%d", &T);
while (T--) {
memset(f, -1, sizeof(f));
int n;
scanf("%d", &n);
printf("%lld\n", dfs(29, 1 << 29, 0, 0, n, 0) * 2);
}
return 0;
}
#if 0
2
6
114514
#endif
总结
这是我本科第一次参加蓝桥杯,我想大概也是最后一次了。
感觉自己还是一如既往地菜,希望以后能不忘初心,继续学习。
如果研究生阶段还有机会参加蓝桥杯的话,希望到时候也可以向那些大佬一样骄傲地说出蓝桥杯 “有手就行、点击就送” 吧。