试题 A: 幸运数
本题总分: 5 5 5 分
【问题描述】
小蓝认为如果一个数含有偶数个数位,并且前面一半的数位之和等于后面一半的数位之和,则这个数是他的幸运数字。例如 2314 2314 2314 是一个幸运数字,因为它有 4 4 4 个数位,并且 2 + 3 = 1 + 4 2 + 3 = 1 + 4 2+3=1+4。现在请你帮他计算从 1 1 1 至 100000000 100000000 100000000 之间共有多少个不同的幸运数字。
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
4430091
根据插板法我们可知整数 n n n拆分成 k k k个非负部分的方案数为 C n + k − 1 k − 1 C_{n+k-1}^{k-1} Cn+k−1k−1,有下界, j j j个部分下界为 x x x时方案数为 C n + k − 1 − j x k − 1 C_{{n+k-1-jx}}^{k-1} Cn+k−1−jxk−1,那么根据容斥原理可知存在一个部分大于 x x x的拆分方案数为 ∑ i = 1 k ( − 1 ) i − 1 C n + i − 1 − i ( x + 1 ) i − 1 \sum_{i=1}^k(-1)^{i-1}C_{n+i-1-i(x+1)}^{i-1} ∑i=1k(−1)i−1Cn+i−1−i(x+1)i−1,记 p ( n , k ) p(n,k) p(n,k)为不存在部分大于 x x x的拆分方案数,有 p ( n , k ) = C n + k − 1 k − 1 − ∑ i = 1 k ( − 1 ) i − 1 C k i C n + i − 1 − i ( x + 1 ) i − 1 = ∑ i = 0 k ( − 1 ) i C k i C n + i − 1 − i ( x + 1 ) i − 1 . p(n,k)=C_{n+k-1}^{k-1}-\sum_{i=1}^k(-1)^{i-1}C_{k}^{i}C_{n+i-1-i(x+1)}^{i-1}=\sum_{i=0}^k(-1)^{i}C_{k}^{i}C_{n+i-1-i(x+1)}^{i-1}. p(n,k)=Cn+k−1k−1−i=1∑k(−1)i−1CkiCn+i−1−i(x+1)i−1=i=0∑k(−1)iCkiCn+i−1−i(x+1)i−1.考虑到幸运数字数位个数为偶,即不能有前缀 0 0 0,于是考虑将 p ( n − 1 , k ) p(n-1,k) p(n−1,k)看作最高位数字大于 1 1 1的方案数,但这样方案数里就包含了高位数字为 10 10 10的方案,显然不合法,所以减去不合法部分 p ( n − 10 , k − 1 ) p(n-10, k -1) p(n−10,k−1),最终答案为 ∑ k = 1 4 ∑ n = 1 9 k ( p ( n − 1 , k ) − p ( n − 10 , k − 1 ) ) ⋅ p ( n , k ) . \sum_{k=1}^4\sum_{n=1}^{9k}\big(p(n-1,k)-p(n-10,k-1)\big)\cdot p(n,k). k=1∑4n=1∑9k(p(n−1,k)−p(n−10,k−1))⋅p(n,k).
#include <stdio.h>
long long C(int n, int m) {
if (m > n || n < 0) return 0;
long long C = 1;
for (int i = 0; i < m; ++i)
C = C * (n - i) / (i + 1);
return C;
}
long long p(int n, int k) {
long long p = 0;
for (int i = 0, sign = 1; i <= k; ++i, sign = -sign)
p += sign * C(k, i) * C(n + k - 1 - i * 10, k - 1);
return p;
}
int main() {
long long ans = 0;
for (int k = 1; k <= 4; ++k)
for (int n = 1; n <= 9 * k; ++n)
ans += (p(n - 1, k) - p(n - 10, k - 1)) * p(n, k);
printf("%lld", ans);
}
优雅,永不过时
但我也数次强调过,在比赛过程中,编写这种程序的性价比是极低的,不如写个暴力程序开个线程让它自己去跑出答案。
#include <stdio.h>
int ans, buffer[9];
int main() {
for (int k = 1; k <= 100000000; ++k) {
int g = 0, n = 0, m = k;
for (; m; m /= 10) buffer[n++] = m % 10;
if (n & 1) continue;
for (int i = 0; i < n >> 1; ++i) g += buffer[i];
for (int i = n >> 1; i < n; ++i) g -= buffer[i];
if (!g) ++ans;
}
printf("%d", ans);
}
试题 B: 有奖问答
本题总分: 5 5 5 分
【问题描述】
小蓝正在参与一个现场问答的节目。活动中一共有
30
30
30 道题目,每题只有答对和答错两种情况,每答对一题得
10
10
10 分,答错一题分数归零。
小蓝可以在任意时刻结束答题并获得目前分数对应的奖项,之后不能再答任何题目。最高奖项需要
100
100
100 分,所以到达
100
100
100 分时小蓝会直接停止答题。
已知小蓝最终实际获得了
70
70
70 分对应的奖项,请问小蓝所有可能的答题情况有多少种?
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
8335366
记 d p i , j dp_{i,j} dpi,j为第 i i i个问题小蓝获得 10 j 10j 10j分的方案数,显然 d p 0 , 0 = 1 dp_{0,0}=1 dp0,0=1,对于问题 i i i,如何小蓝回答错误,那么 d p i , 0 = ∑ j = 0 9 d p i − 1 , j dp_{i,0}=\sum_{j=0}^9dp_{i-1,j} dpi,0=∑j=09dpi−1,j,因为当小明有 10 ⋅ 10 10\cdot 10 10⋅10分时问答结束故不可转移,当小明回答对时则 d p i , j = d p i − 1 , j − 1 dp_{i,j}=dp_{i-1,j-1} dpi,j=dpi−1,j−1,最终,答案为 ∑ i = 1 30 d p i , 7 \sum_{i=1}^{30}dp_{i,7} ∑i=130dpi,7。
#include <stdio.h>
int ans = 0, dp[11] = { 1 };
int main() {
for (int i = 1; i <= 30; ++i) {
int loss = 0;
for (int j = 10; j; --j) {
loss += dp[j - 1];
dp[j] = dp[j - 1];
}
dp[0] = loss;
ans += dp[7];
}
printf("%d", ans);
}
做了个滚动数组,当然也可以暴搜。
#include <stdio.h>
int n = 30, ans = 0, target = 70;
void dfs(int depth, int score) {
if (depth > n) return;
if (score == 100) return;
if (score == target) ++ans;
dfs(depth + 1, score + 10);
dfs(depth + 1, 0);
}
int main() {
printf("%d", (dfs(0, 0), ans));
}
试题 C: 平方差
时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 256.0 M B 256.0\mathrm{MB} 256.0MB 本题总分: 10 10 10 分
【问题描述】
给定 L , R L, R L,R,问 L ≤ x ≤ R L ≤ x ≤ R L≤x≤R 中有多少个数 x x x 满足存在整数 y , z y,z y,z 使得 x = y 2 − z 2 x = y^2 − z^2 x=y2−z2。
【输入格式】
输入一行包含两个整数 L, R,用一个空格分隔。
【输出格式】
输出一行包含一个整数满足题目给定条件的 x x x 的数量。
【样例输入】
1 5
【样例输出】
4
【样例说明】
1
=
1
2
−
0
2
;
1 = 1^2 − 0^2;
1=12−02;
3
=
2
2
−
1
2
;
3 = 2^2 − 1^2;
3=22−12;
4
=
2
2
−
0
2
;
4 = 2^2 − 0^2;
4=22−02;
5
=
3
2
−
2
2
。
5 = 3^2 − 2^2 。
5=32−22。
【评测用例规模与约定】
对于
40
%
40\%
40% 的评测用例,
L
R
≤
5000
L R ≤ 5000
LR≤5000;
对于所有评测用例,
1
≤
L
≤
R
≤
1
0
9
1 ≤ L ≤ R ≤ 10^9
1≤L≤R≤109。
基本数论
不知道基不基本,反正我老民科没见过,顺手推推。
对偶数
a
a
a ,平方数都可以表示成
2
2
(
a
2
)
2
2^2(\frac a2)^2
22(2a)2,即
a
2
≡
0
(
m
o
d
4
)
a^2 \equiv 0\pmod 4
a2≡0(mod4);对奇数
b
b
b ,平方数都可以表示成
(
b
−
1
)
2
+
2
b
−
1
(b-1)^2+2b-1
(b−1)2+2b−1,即
b
2
≡
1
(
m
o
d
4
)
b^2 \equiv 1\pmod 4
b2≡1(mod4);
那么就奇偶性而言
a
−
a
、
b
−
a
、
a
−
b
a-a、b-a、a-b
a−a、b−a、a−b 的结果为
0
、
1
、
3
(
m
o
d
4
)
0、1、3\pmod 4
0、1、3(mod4),容易得知
x
x
x 不可能被分解成
4
c
+
2
4c + 2
4c+2 的形式。那么
4
c
+
k
,
0
≤
4
c
+
k
≤
R
,
k
∈
{
0
,
1
,
3
}
4c+k,0\leq 4c+k\leq R,k\in\{0,1,3\}
4c+k,0≤4c+k≤R,k∈{0,1,3} 一定可以被
y
,
z
,
0
≤
z
<
y
≤
R
y,z,0\leq z<y\leq R
y,z,0≤z<y≤R 表示吗?
首先将
4
c
+
k
4c+k
4c+k 划分为
2
d
−
1
2d-1
2d−1 和
4
c
4c
4c。对于正奇数集
2
d
−
1
2d-1
2d−1,容易构造
y
=
d
,
z
=
d
−
1
,
x
=
(
y
−
z
)
(
y
+
z
)
=
2
d
−
1
y=d,z=d-1,x=(y-z)(y+z)=2d-1
y=d,z=d−1,x=(y−z)(y+z)=2d−1,
y
2
−
z
2
y^2-z^2
y2−z2 可表不大于
2
R
−
1
2R-1
2R−1 的正奇数。对于
4
c
4c
4c,我们令
y
=
c
,
z
=
c
−
2
y = c, z=c-2
y=c,z=c−2 则
x
=
(
y
−
z
)
(
y
+
z
)
=
4
c
−
4
x=(y-z)(y+z)=4c-4
x=(y−z)(y+z)=4c−4,
y
2
−
z
2
y^2-z^2
y2−z2 可表不大于
4
R
−
4
4R-4
4R−4 的
4
4
4 的倍数。综上
x
≠
4
c
+
2
x\not=4c+2
x=4c+2 时一定可用被
y
2
−
z
2
y^2-z^2
y2−z2 表示。
#include <stdio.h>
int L, R;
int main() {
scanf("%d %d", &L, &R);
printf("%d",
R - (R + 2) / 4
- (L - 1 - (L - 1 + 2) / 4));
}
试题 D: 更小的数
时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 256.0 M B 256.0\mathrm{MB} 256.0MB 本题总分: 10 10 10 分
【问题描述】
小蓝有一个长度均为
n
n
n 且仅由数字字符
0
∼
9
0 \sim 9
0∼9 组成的字符串,下标从
0
0
0 到
n
−
1
n − 1
n−1,你可以将其视作是一个具有
n
n
n 位的十进制数字
n
u
m
num
num,小蓝可以从
n
u
m
num
num 中选出一段连续的子串并将子串进行反转,最多反转一次。小蓝想要将选出的子串进行反转后再放入原位置处得到的新的数字
n
u
m
n
e
w
num_{new}
numnew 满足条件
n
u
m
n
e
w
<
n
u
m
num_{new} < num
numnew<num,请你帮他计算下一共有多少种不同的子串选择方案,只要两个子串在
n
u
m
num
num 中的位置不完全相同我们就视作是不同的方案。
注意,我们允许前导零的存在,即数字的最高位可以是
0
0
0,这是合法的。
【输入格式】
输入一行包含一个长度为 n n n 的字符串表示 n u m num num(仅包含数字字符 0 ∼ 9 0 \sim 9 0∼9),从左至右下标依次为 0 ∼ n − 1 0 \sim n − 1 0∼n−1。
【输出格式】
输出一行包含一个整数表示答案。
【样例输入】
210102
【样例输出】
8
【样例说明】
一共有
8
8
8 种不同的方案:
1
)
1)
1)所选择的子串下标为
0
∼
1
0 \sim 1
0∼1,反转后的
n
u
m
n
e
w
=
120102
<
210102
num_{new} = 120102 < 210102
numnew=120102<210102;
2
)
2)
2)所选择的子串下标为
0
∼
2
0 \sim 2
0∼2,反转后的
n
u
m
n
e
w
=
012102
<
210102
num_{new} = 012102 < 210102
numnew=012102<210102;
3
)
3)
3)所选择的子串下标为
0
∼
3
0 \sim 3
0∼3,反转后的
n
u
m
n
e
w
=
101202
<
210102
num_{new} = 101202 < 210102
numnew=101202<210102;
4
)
4)
4)所选择的子串下标为
0
∼
4
0 \sim 4
0∼4,反转后的
n
u
m
n
e
w
=
010122
<
210102
num_{new} = 010122 < 210102
numnew=010122<210102;
5
)
5)
5)所选择的子串下标为
0
∼
5
0 \sim 5
0∼5,反转后的
n
u
m
n
e
w
=
201012
<
210102
num_{new} = 201012 < 210102
numnew=201012<210102;
6
)
6)
6)所选择的子串下标为
1
∼
2
1 \sim 2
1∼2,反转后的
n
u
m
n
e
w
=
201102
<
210102
num_{new} = 201102 < 210102
numnew=201102<210102;
7
)
7)
7)所选择的子串下标为
1
∼
4
1 \sim 4
1∼4,反转后的
n
u
m
n
e
w
=
201012
<
210102
num_{new} = 201012 < 210102
numnew=201012<210102;
8
)
8)
8)所选择的子串下标为
3
∼
4
3 \sim 4
3∼4,反转后的
n
u
m
n
e
w
=
210012
<
210102
num_{new} = 210012 < 210102
numnew=210012<210102;
【评测用例规模与约定】
对于
20
%
20\%
20% 的评测用例,
1
≤
n
≤
100
1 ≤ n ≤ 100
1≤n≤100;
对于
40
%
40\%
40% 的评测用例,
1
≤
n
≤
1000
1 ≤ n ≤ 1000
1≤n≤1000;
对于所有评测用例,
1
≤
n
≤
5000
1 ≤ n ≤ 5000
1≤n≤5000。
[
l
,
r
]
[l,r]
[l,r] 的翻转可以由
(
l
,
r
)
(l,r)
(l,r) 在常数复杂度意义下转移过来,具体地说:
记
n
u
m
[
l
,
r
]
num[l,r]
num[l,r] 为下标
l
∼
r
l\sim r
l∼r 构成的子串的数值,
i
n
v
[
l
,
r
]
inv[l,r]
inv[l,r] 为下标
l
∼
r
l\sim r
l∼r 构成的子串翻转后的数值,则
i
n
v
[
l
,
r
]
=
n
u
m
[
r
]
×
1
0
r
−
l
+
i
n
v
(
l
,
r
)
+
n
u
m
[
l
]
inv[l,r]=num[r]\times 10^{r-l}+inv(l,r)+num[l]
inv[l,r]=num[r]×10r−l+inv(l,r)+num[l],容易发现
[
l
,
r
]
[l,r]
[l,r] 的翻转与原串的大小关系也可以由
(
l
,
r
)
(l,r)
(l,r) 在常数复杂度意义下转移过来:
当
n
u
m
[
r
]
>
n
u
m
[
l
]
num[r] >num[l]
num[r]>num[l] 时,
n
u
m
[
r
]
≠
0
num[r]\not=0
num[r]=0 则有
n
u
m
[
r
]
×
1
0
r
−
l
>
n
u
m
[
l
,
r
]
num[r]\times 10^{r-l}>num[l,r]
num[r]×10r−l>num[l,r] 即
i
n
v
[
l
,
r
]
>
n
u
m
[
l
,
r
]
inv[l,r]>num[l,r]
inv[l,r]>num[l,r];
当
n
u
m
[
r
]
=
n
u
m
[
l
]
num[r] =num[l]
num[r]=num[l] 时,则有
∣
i
n
v
[
l
,
r
]
>
n
u
m
[
l
,
r
]
∣
=
∣
i
n
v
(
l
,
r
)
>
n
u
m
(
l
,
r
)
∣
|inv[l,r]>num[l,r]|=|inv(l,r)>num(l,r)|
∣inv[l,r]>num[l,r]∣=∣inv(l,r)>num(l,r)∣,当
E
E
E 成立时,
∣
E
∣
=
1
|E|=1
∣E∣=1,否则等于
0
0
0;
当
n
u
m
[
r
]
<
n
u
m
[
l
]
num[r] <num[l]
num[r]<num[l] 时,依
n
u
m
[
r
]
>
n
u
m
[
l
]
num[r] >num[l]
num[r]>num[l] 的判别过程,有
i
n
v
[
l
,
r
]
<
n
u
m
[
l
,
r
]
inv[l,r]<num[l,r]
inv[l,r]<num[l,r];
于是我们可以枚举每个元素,累加依该元素为中位元素的子序列对答案的贡献。
#include <stdio.h>
#include <string.h>
char num[5010]{1};
int main() {
gets(num + 1);
int n = strlen(num), ans = 0;
for (int k = 2; k < n; ++k)
for (int i = k, j = k, e1 = 0, e2 = 0; i > 0 && j < n; --i, ++j) {
if (num[j] != num[i - 1]) e1 = num[j] < num[i - 1];
if (num[j] != num[i]) e2 = num[j] < num[i];
ans += e1 + e2;
}
printf("%d", ans);
}
试题 E: 颜色平衡树
时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 256.0 M B 256.0\mathrm{MB} 256.0MB 本题总分: 15 15 15 分
【问题描述】
给定一棵树,结点由
1
1
1 至
n
n
n 编号,其中结点
1
1
1 是树根。树的每个点有一个颜色
C
i
C_i
Ci。
如果一棵树中存在的每种颜色的结点个数都相同,则我们称它是一棵颜色平衡树。
求出这棵树中有多少个子树是颜色平衡树。
【输入格式】
输入的第一行包含一个整数
n
n
n,表示树的结点数。
接下来
n
n
n 行,每行包含两个整数
C
i
,
F
i
C_i, F_i
Ci,Fi,用一个空格分隔,表示第
i
i
i 个结点的颜色和父亲结点编号。
特别地,输入数据保证
F
1
F_1
F1 为
0
0
0 ,也即
1
1
1 号点没有父亲结点。保证输入数据是一棵树。
【输出格式】
输出一行包含一个整数表示答案。
【样例输入】
6
2 0
2 1
1 2
3 3
3 4
1 4
【样例输出】
4
【样例说明】
编号为 1 , 3 , 5 , 6 1, 3, 5, 6 1,3,5,6 的 4 4 4 个结点对应的子树为颜色平衡树。
【评测用例规模与约定】
对于
30
%
30\%
30% 的评测用例,
n
≤
200
,
C
i
≤
200
n ≤ 200,C_i ≤ 200
n≤200,Ci≤200;
对于
60
%
60\%
60% 的评测用例,
n
≤
5000
,
C
i
≤
5000
n ≤ 5000,C_i ≤ 5000
n≤5000,Ci≤5000;
对于所有评测用例,
1
≤
n
≤
200000
,
1
≤
C
i
≤
200000
,
0
≤
F
i
<
i
1 ≤ n ≤ 200000,1 ≤ C_i ≤ 200000,0 ≤ F_i < i
1≤n≤200000,1≤Ci≤200000,0≤Fi<i。
启发式合并模板题
#include <stdio.h>
#include <set>
const int N = 200005;
int n, f, ans, cs[N], col[N], head[N], next[N];
int dfn = 0, ld[N], rd[N], dtv[N], siz[N], son[N];
std::multiset<int> sc;
inline void add(int i) {
if (cs[col[i]]) sc.erase(sc.find(cs[col[i]]));
sc.insert(++cs[col[i]]);
}
inline void add(int l, int r) { for (; l <= r; ++l) add(dtv[l]); }
inline void del(int l, int r) {
for (; l <= r; ++l) {
sc.erase(sc.find(cs[col[dtv[l]]]));
if (--cs[col[dtv[l]]]) sc.insert(cs[col[dtv[l]]]);
}
}
void dfs0(int u) {
ld[u] = ++dfn;
dtv[dfn] = u;
siz[u] = 1;
for (int v = head[u]; v; v = next[v]) {
dfs0(v);
siz[u] += siz[v];
if (!son[u] || siz[son[u]] < siz[v]) son[u] = v;
}
rd[u] = dfn;
}
void dfs1(int u, bool keep) {
for (int v = head[u]; v; v = next[v])
if (v != son[u]) dfs1(v, 0);
if (son[u]) dfs1(son[u], 1);
for (int v = head[u]; v; v = next[v])
if (v != son[u]) add(ld[v], rd[v]);
add(u);
if (*sc.begin() == *sc.rbegin()) ++ans;
if (!keep) del(ld[u], rd[u]);
}
int main() {
scanf("%d %d %d", &n, &col[1], &f);
for (int i = 2; i <= n; ++i)
scanf("%d %d", &col[i], &f), next[i] = head[f], head[f] = i;
dfs0(1);
dfs1(1, 0);
printf("%d", ans);
}
不过观察到树根一定为 1 1 1 且 F i < i F_i < i Fi<i,于是可用链式前向星构建有向图来表示数,减小程序的运行常数,毕竟 n n n 在 2 × 1 0 5 2\times10^5 2×105 常数大了很有可能会爆栈或者超一点点时。
试题 F: 买瓜
时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 256.0 M B 256.0\mathrm{MB} 256.0MB 本题总分: 15 15 15 分
【问题描述】
小蓝正在一个瓜摊上买瓜。瓜摊上共有
n
n
n 个瓜,每个瓜的重量为
A
i
A_i
Ai。
小蓝刀功了得,他可以把任何瓜劈成完全等重的两份,不过每个瓜只能劈一刀。
小蓝希望买到的瓜的重量的和恰好为
m
m
m。
请问小蓝至少要劈多少个瓜才能买到重量恰好为
m
m
m 的瓜。如果无论怎样小蓝都无法得到总重恰好为
m
m
m 的瓜,请输出
−
1
−1
−1。
【输入格式】
输入的第一行包含两个整数
n
,
m
n, m
n,m,用一个空格分隔,分别表示瓜的个数和小蓝想买到的瓜的总重量。
第二行包含
n
n
n 个整数
A
i
A_i
Ai,相邻整数之间使用一个空格分隔,分别表示每个瓜的重量。
【输出格式】
输出一行包含一个整数表示答案。
【样例输入】
3 10
1 3 13
【样例输出】
2
【评测用例规模与约定】
对于
20
%
20\%
20% 的评测用例,
∑
n
≤
10
\sum n ≤ 10
∑n≤10;
对于
60
%
60\%
60% 的评测用例,
∑
n
≤
20
\sum n ≤ 20
∑n≤20;
对于所有评测用例,
1
≤
n
≤
30
,
1
≤
A
i
≤
1
0
9
,
1
≤
m
≤
1
0
9
1 ≤ n ≤ 30,1 ≤ A_i ≤ 10^9 ,1 ≤ m ≤ 10^9
1≤n≤30,1≤Ai≤109,1≤m≤109。
折半枚举,内存和时间都挺紧张的,就不开map
转用 二分+布隆过滤器,交了发到 dotcpp 上去,效果好像还行。
复杂度的话一半的枚举是
O
(
3
n
2
)
O(3^\frac n2)
O(32n),另一半要二分查找所以是
O
(
n
2
3
n
2
log
3
)
O(\frac n23^\frac n2\log3)
O(2n32nlog3)。
#include <stdio.h>
#include <algorithm>
#include <bitset>
const int HALF = 14348907;
int n, m, h, H, ans, a[30];
std::bitset<400000009> bloom;
std::pair<int, int> half[HALF];
void make(int i, int w, int k) {
if (i < 0) half[H++] = {w, k}, bloom[w % 400000009] = 1;
else {
make(i - 1, w, k);
if (w <= m - a[i])
make(i - 1, w + a[i], k + 1);
if (w <= m - (a[i] << 1))
make(i - 1, w + (a[i] << 1), k);
}
}
void dfs(int i, int w, int k) {
if (i == n) {
if (bloom[w % 400000009]) {
int l = 0, r = h;
while (l < r) {
int mid = (l + r) >> 1;
if (half[mid].first < w) l = mid + 1;
else r = mid;
}
if (half[l].first == w)
ans = std::min(ans, half[l].second + k);
}
} else {
dfs(i + 1, w, k);
if (w >= a[i])
dfs(i + 1, w - a[i], k + 1);
if (w >= (a[i] << 1))
dfs(i + 1, w - (a[i] << 1), k);
}
}
int main() {
scanf("%d %d", &n, &m), m <<= 1, ans = n << 1;
for (int i = 0; i < n; ++i) scanf("%d", &a[i]);
std::random_shuffle(a, a + n);
make(n / 2, 0, 0);
std::sort(half, half + H);
for (int i = 1; i < H; ++i)
if (half[i].first != half[h].first) half[++h] = half[i];
dfs(n / 2 + 1, m, 0);
printf("%d", ans > n ? -1 : ans);
}
试题 G: 网络稳定性
时间限制: 1.5 s 1.5\mathrm s 1.5s 内存限制: 256.0 M B 256.0\mathrm{MB} 256.0MB 本题总分: 20 20 20 分
【问题描述】
有一个局域网,由
n
n
n 个设备和
m
m
m 条物理连接组成,第
i
i
i 条连接的稳定性为
w
i
w_i
wi。
对于从设备
A
A
A 到设备
B
B
B 的一条经过了若干个物理连接的路径,我们记这条路径的稳定性为其经过所有连接中稳定性最低的那个。
我们记设备
A
A
A 到设备
B
B
B 之间通信的稳定性为
A
A
A 至
B
B
B 的所有可行路径的稳定性中最高的那一条。
给定局域网中的设备的物理连接情况,求出若干组设备
x
i
x_i
xi 和
y
i
y_i
yi 之间的通信稳定性。如果两台设备之间不存在任何路径,请输出
−
1
−1
−1。
【输入格式】
输入的第一行包含三个整数
n
,
m
,
q
n, m, q
n,m,q,分别表示设备数、物理连接数和询问数。
接下来
m
m
m 行,每行包含三个整数
u
i
,
v
i
,
w
i
u_i, v_i,w_i
ui,vi,wi,分别表示
u
i
u_i
ui 和
v
i
v_i
vi 之间有一条稳定性为
w
i
w_i
wi 的物理连接。
接下来
q
q
q 行,每行包含两个整数
x
i
,
y
i
x_i, y_i
xi,yi,表示查询
x
i
x_i
xi 和
y
i
y_i
yi 之间的通信稳定性。
【输出格式】
输出 q q q 行,每行包含一个整数依次表示每个询问的答案。
【样例输入】
5 4 3
1 2 5
2 3 6
3 4 1
1 4 3
1 5
2 4
1 3
【样例输出】
-1
3
5
【评测用例规模与约定】
对于
30
%
30\%
30% 的评测用例,
n
,
q
≤
500
,
m
≤
1000
n, q ≤ 500,m ≤ 1000
n,q≤500,m≤1000;
对于
60
%
60\%
60% 的评测用例,
n
,
q
≤
5000
,
m
≤
10000
n, q ≤ 5000,m ≤ 10000
n,q≤5000,m≤10000;
对于所有评测用例,
2
≤
n
,
q
≤
1
0
5
,
1
≤
m
≤
3
×
1
0
5
,
1
≤
u
i
,
v
i
,
x
i
,
y
i
≤
n
,
1
≤
w
i
≤
1
0
6
,
u
i
≠
v
i
,
x
i
,
≠
y
i
2 ≤ n, q ≤ 10^5,1 ≤ m ≤ 3 × 10^5,1 ≤ u_i, v_i, x_i, y_i ≤ n,1 ≤ w_i ≤ 10^6,u_i \not=v_i,x_i ,\not=y_i
2≤n,q≤105,1≤m≤3×105,1≤ui,vi,xi,yi≤n,1≤wi≤106,ui=vi,xi,=yi。
最大生成树模板,通过 K r u s k a l \rm Kruskal Kruskal 算法易知最新应加入生成树的边 u ↔ w v u\xleftrightarrow{w}v uw v 是权重最小的边,考虑新建一个节点 x x x,将 x ↔ w u 、 x ↔ w v x\xleftrightarrow{w}u、x\xleftrightarrow{w}v xw u、xw v 加入生成树,容易原先森林里连通的量的通信稳定性显然不变,最近连通的量根据最小生成树的最优性它们的稳定性一定为 w w w,于是我们令 x x x 为树根,并将 w w w 上浮到入点,重复这个过程,最终可用看到所有节点对它们的通信稳定度为它们的最近公共祖先的权重。
#include <stdio.h>
#include <algorithm>
const int N = 100005;
struct edge {
int u, v, w;
inline bool operator<(edge e) { return w > e.w; };
} edges[3 * N];
int head[N << 1], query[N], next[N << 2], ver[N << 2], wei[N << 1];
int n, m, p, q, cur, ans[N], linked[N << 1], w[N << 1], buffer[N << 1];
int find(int x) { return x == linked[x] ? x : (linked[x] = find(linked[x])); }
bool vis[N << 1];
void dfs(int u) {
linked[u] = u;
buffer[p++] = u;
vis[u] = 1;
for (int i = head[u]; i; i = next[i])
dfs(ver[i]), linked[ver[i]] = u;
for (int i = query[u]; i; i = next[i])
if (vis[ver[i]]) ans[wei[i]] = w[find(ver[i])];
}
int main() {
scanf("%d %d %d", &n, &m, &q);
for (int i = 0; i < m; ++i)
scanf("%d %d %d", &edges[i].u, &edges[i].v, &edges[i].w);
for (int i = 0, x, y; i < q; ++i) {
scanf("%d %d", &x, &y);
next[++cur] = query[x], query[x] = cur, ver[cur] = y, wei[cur] = i;
next[++cur] = query[y], query[y] = cur, ver[cur] = x, wei[cur] = i;
}
std::sort(edges, edges + m);
for (int i = (n << 1) - 1; i; --i) linked[i] = i;
for (int i = 0, x = n; i < m; ++i) {
int u = find(edges[i].u), v = find(edges[i].v);
if (u == v) continue;
w[++x] = edges[i].w;
linked[u] = linked[v] = x;
next[++cur] = head[x], head[x] = cur, ver[cur] = u;
next[++cur] = head[x], head[x] = cur, ver[cur] = v;
}
for (int i = (n << 1) - 1; i; --i, p = 0) {
if (linked[i] == i) dfs(i);
while (p--) vis[buffer[p]] = 0;
}
for (int i = 0; i < q; ++i)
printf("%d\n", ans[i] ? ans[i] : -1);
}
t a r j a n \rm tarjan tarjan!永远滴神
试题 H: 异或和之和
时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 256.0 M B 256.0\mathrm{MB} 256.0MB 本题总分: 20 20 20 分
【问题描述】
给定一个数组 A i A_i Ai,分别求其每个子段的异或和,并求出它们的和。或者说,对于每组满足 1 ≤ L ≤ R ≤ n 1 ≤ L ≤ R ≤ n 1≤L≤R≤n 的 L , R L, R L,R,求出数组中第 L L L 至第 R R R 个元素的异或和。然后输出每组 L , R L, R L,R 得到的结果加起来的值。
【输入格式】
输入的第一行包含一个整数
n
n
n。
第二行包含
n
n
n 个整数
A
i
A_i
Ai,相邻整数之间使用一个空格分隔。
【输出格式】
输出一行包含一个整数表示答案。
【样例输入】
5
1 2 3 4 5
【样例输出】
39
【评测用例规模与约定】
对于
30
%
30\%
30% 的评测用例,
n
≤
300
n ≤ 300
n≤300;
对于
60
%
60\%
60% 的评测用例,
n
≤
5000
n ≤ 5000
n≤5000;
对于所有评测用例,
1
≤
n
≤
1
0
5
,
0
≤
A
i
≤
2
20
1 ≤ n ≤ 10^5,0 ≤ A_i ≤ 2^{20}
1≤n≤105,0≤Ai≤220。
令
A
i
=
∑
k
=
0
20
a
i
,
k
2
k
,
a
k
∈
{
0
,
1
}
A_i = \sum_{k=0}^{20}a_{i,k}2^k,a_k\in\{0,1\}
Ai=∑k=020ai,k2k,ak∈{0,1},由于按位运算不进位的特性,则
∑
1
≤
L
≤
R
≤
n
⨁
i
=
L
R
A
i
=
∑
k
=
0
20
∑
1
≤
L
≤
R
≤
n
⨁
i
=
L
R
a
i
,
k
\sum_{\substack{1\leq L\leq R\leq n}}\bigoplus_{i=L}^RA_i=\sum_{k=0}^{20}\sum_{\substack{1\leq L\leq R\leq n}}\bigoplus_{i=L}^Ra_{i,k}
∑1≤L≤R≤n⨁i=LRAi=∑k=020∑1≤L≤R≤n⨁i=LRai,k,而
a
L
,
k
⊕
a
L
+
1
,
k
⊕
⋯
⊕
a
R
,
k
a_{L,k}\oplus a_{L+1,k}\oplus\cdots\oplus a_{R,k}
aL,k⊕aL+1,k⊕⋯⊕aR,k 当
a
i
,
k
a_{i,k}
ai,k 奇数个非零时为
2
k
2^k
2k,否则为零,于是枚举每一个
a
i
,
k
a_{i,k}
ai,k 将其非零值出现次序的奇偶划分累加,并根据之前累加结果计算出以当前元素为结尾的子序列对答案的贡献。
可以说老模板了。
#include <stdio.h>
int n, a[100000];
int main() {
scanf("%d", &n);
for (int i = 0; i < n; ++i) scanf("%d", &a[i]);
long long ans = 0;
for (int k = 0; k <= 20; ++k) {
int cnt[]{ 1, 0 }, j = 0;
long long sum = 0;
for (int i = 0; i < n; ++i) {
j ^= (a[i] >> k) & 1;
sum += cnt[j ^ 1];
cnt[j]++;
}
ans += sum << k;
}
printf("%lld", ans);
}
试题 I: 像素放置
时间限制: 1.0 s 1.0\mathrm s 1.0s 内存限制: 256.0 M B 256.0\mathrm{MB} 256.0MB 本题总分: 25 25 25 分
【问题描述】
小蓝最近迷上了一款名为《像素放置》的游戏,游戏在一个 n × m 的网格棋盘上进行,棋盘含有 n 行,每行包含 m 个方格。玩家的任务就是需要对这 n × m 个方格进行像素填充,填充颜色只有黑色或白色两种。有些方格中会出现一个整数数字 x(0 ≤ x ≤ 9),这表示当前方格加上周围八个方向上相邻的方格(分别是上方、下方、左方、右方、左上方、右上方、左下方、右下方)共九个方格内有且仅有 x 个方格需要用黑色填充。
玩家需要在满足所有数字约束下对网格进行像素填充,请你帮助小蓝来完成。题目保证所有数据都有解并且解是唯一的。
【输入格式】
输入的第一行包含两个整数 n, m,用一个空格分隔,表示棋盘大小。
接下来 n 行,每行包含 m 个字符,表示棋盘布局。字符可能是数字 0 ∼ 9,这表示网格上的数字;字符还有可能是下划线(ASCII 码为 95 ),表示一个不带有数字的普通网格。
【输出格式】
输出 n 行,每行包含 m 个字符,表示答案。如果网格填充白色则用字符 0 表示,如果网格填充黑色则用字符 1 表示。
【样例输入】
6 8
_1__5_1_
1_4__42_
3__6__5_
___56___
_688___4
_____6__
【样例输出】
00011000
00111100
01000010
11111111
01011110
01111110
【样例说明】
【评测用例规模与约定】
对于
50
%
50\%
50% 的评测用例,
1
≤
n
,
m
≤
5
1 ≤ n, m ≤ 5
1≤n,m≤5;
对于所有评测用例,
1
≤
n
,
m
≤
10
1 ≤ n, m ≤ 10
1≤n,m≤10。
状压DP
轮廓线
D
P
\,\small\rm DP
DP,定义
d
p
i
,
j
,
s
dp_{i,j,s}
dpi,j,s 为
(
i
,
j
)
(i,j)
(i,j) 格前
2
(
m
+
1
)
2(m+1)
2(m+1) 格排列方案为
s
s
s 时是否存在
(
i
−
1
,
j
−
1
)
(i-1,j-1)
(i−1,j−1) 绝对合法的方案。
可以看到,若是用
01
01
01 来表示
s
s
s,则
s
s
s 的
0
,
1
,
m
−
1
,
m
,
m
+
1
,
2
m
−
1
,
2
m
,
2
m
+
1
0,1,m-1,m,m+1,2m-1,2m,2m+1
0,1,m−1,m,m+1,2m−1,2m,2m+1 数位之和,加上目前
(
i
,
j
)
(i,j)
(i,j) 放置与否与原方格中
(
i
−
1
,
j
−
1
)
(i-1,j-1)
(i−1,j−1) 的数值相比较,就能判断
(
i
−
1
,
j
−
1
)
(i-1,j-1)
(i−1,j−1) 的合法性,事实上还可以加一步对
(
i
−
1
,
j
−
1
)
(i-1,j-1)
(i−1,j−1) 后面的方格的违法校验,但这样常数变大时间复杂度上界不变,没必要。
不过这样一共存在
j
∈
{
1
,
2
,
m
}
j\in\{1,2,m\}
j∈{1,2,m} 三种特殊情况。
对于第一种情况我们直接转移状态,对于第二种情况我们特殊计算左上角方格是否合法,对于最后一种情况我们在一行处理完毕后做一次矫正,复杂度在 O ( n m 2 2 m + 2 ) O(nm2^{2m+2}) O(nm22m+2),感觉有的够呛,这场题目的时间都开的很极限。
#include <stdio.h>
#include <bitset>
char grid[11][12], *mp, mp8[1 << 22], mp5[1 << 22], last[11];
std::bitset<1 << 22> dp[101]{1};
bool ans[11][11];
int n, m;
inline int get(int n, int i) { return (n >> i) & 1; }
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; ++i) {
scanf("%s", grid[i] + 1);
for (int j = 1; j <= m; ++j)
if (grid[i][j] != '_') grid[i][j] -= '0';
else grid[i][j] = 0;
}
int pm2 = 1 << (2 * m + 2), mod = pm2 - 1;
for (int i = 0; i < pm2; ++i) {
mp5[i] = get(i, m - 1) + get(i, 2 * m - 1);
if (m > 1) mp5[i] += get(i, 0) + get(i, m) + get(i, 2 * m);
mp8[i] = mp5[i] + get(i, 1) + get(i, m + 1) + get(i, 2 * m + 1);
}
int now = 0, old;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
old = now, ++now;
mp = j != 2 ? mp8 : mp5;
for (int s = 0; s < pm2; ++s) dp[now][s] = 0;
if (grid[i - 1][j - 1]) {
char temp = grid[i - 1][j - 1], temp1 = temp - 1;
for (int s = 0; s < pm2; ++s)
if (dp[old][s]) {
if (temp == mp[s]) dp[now][(s << 1) & mod] = 1;
else if (temp1 == mp[s]) dp[now][((s << 1) | 1) & mod] = 1;
}
} else {
for (int s = 0; s < pm2; ++s)
if (dp[old][s]) dp[now][(s << 1) & mod] = dp[now][((s << 1) | 1) & mod] = 1;
}
}
for (int s = 0; s < pm2; ++s)
if (dp[now][s] && grid[i - 1][m]) dp[now][s] = grid[i - 1][m] == mp5[s >> 1] + (s & 1);
}
int s = 0;
for (; s < pm2;++s)
if (dp[now][s]) {
bool flag = 1;
for (int i = 1; i <= m; ++i) last[i] = get(s, m - i) + get(s, 2 * m - i);
for (int i = 1; i <= m; ++i)
if (grid[n][i] && last[i - 1] + last[i] + last[i + 1] != grid[n][i]) {
flag = 0;
break;
}
if (flag) break;
}
for (int i = n; i; --i)
for (int j = m; j; --j) {
mp = j != 2 ? mp8 : mp5;
ans[i][j] = s & 1;
--now, s = s >> 1;
if ((grid[i - 1][j - 1] && mp[s] + ans[i][j] != grid[i - 1][j - 1]) || !dp[now][s]) s |= pm2 >> 1;
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) putchar('0' ^ ans[i][j]);
putchar('\n');
}
}
改成对应的记忆化搜索可以通过民间数据,推测原因在于唯一解的数据很难捏,总之是道很粪的题。
#include <stdio.h>
#include <bitset>
char grid[11][12], mp8[1 << 22], mp5[1 << 22], last[11];
std::bitset<1 << 22> mark[11][11];
int n, m, mod, ans[11];
inline int get(int n, int i) { return (n >> i) & 1; }
bool dfs(int i, int j, int s) {
if (i == n + 1) {
for (int i = 1; i <= m; ++i) last[i] = get(s, m - i) + get(s, 2 * m - i);
for (int i = 1; i <= m; ++i)
if (grid[n][i] && last[i - 1] + last[i] + last[i + 1] != grid[n][i]) return 0;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) putchar('0' ^ get(ans[i], m - j));
putchar('\n');
}
return 1;
}
if (mark[i][j][s]) return 0;
mark[i][j][s] = 1;
for (int b : {0, 1}) {
if (grid[i - 1][j - 1] && grid[i - 1][j - 1] != (j != 2 ? mp8 : mp5)[s] + b) continue;
if (j < m) {
if (dfs(i, j + 1, ((s << 1) | b) & mod)) return 1;
} else {
if (grid[i - 1][m] && grid[i - 1][m] != mp5[s] + b) continue;
if (dfs(i + 1, 1, ans[i] = ((s << 1) | b) & mod)) return 1;
}
}
return 0;
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; ++i) {
scanf("%s", grid[i] + 1);
for (int j = 1; j <= m; ++j) {
if (grid[i][j] != '_') grid[i][j] -= '0';
else grid[i][j] = 0;
}
}
mod = (1 << (2 * m + 2)) - 1;
for (int i = 0; i <= mod; ++i) {
mp5[i] = get(i, m - 1) + get(i, 2 * m - 1);
if (m > 1) mp5[i] += get(i, 0) + get(i, m) + get(i, 2 * m);
mp8[i] = mp5[i] + get(i, 1) + get(i, m + 1) + get(i, 2 * m + 1);
}
dfs(1, 1, 0);
}
试题 J: 翻转硬币
时间限制: 3.0 s 3.0\mathrm s 3.0s 内存限制: 256.0 M B 256.0\mathrm{MB} 256.0MB 本题总分: 25 25 25 分
【问题描述】
给定 n 个按顺序摆好的硬币,一开始只有第 1 个硬币朝下,其他硬币均朝上。你每次操作可以选择任何一个整数 i 并将所有满足 j mod i = 0 的位置 j 的硬币翻转。
求最少需要多少次操作可以让所有硬币都朝上。
【输入格式】
输入一行包含一个整数 n 。
【输出格式】
输出一行包含一个整数表示最少需要的操作次数。
【样例输入 1】
7
【样例输出 1】
6
【样例输入 2】
1131796
【样例输出 2】
688042
【评测用例规模与约定】
对于 30% 的评测用例,
n
≤
5
×
1
0
6
n ≤ 5 × 10^6
n≤5×106;
对于 70% 的评测用例,
n
≤
1
0
9
n ≤ 10^9
n≤109;
对于所有评测用例,
1
≤
n
≤
1
0
18
1 ≤ n ≤ 10^{18}
1≤n≤1018。
因为无法再取到
1
1
1 以外令
1
mod
i
=
0
1 \operatorname{mod}\, i =0
1modi=0 的值,所有我们一开始翻转
i
=
1
i=1
i=1 及所有硬币,同样的无法取到
2
2
2 以外令
2
mod
i
=
0
2 \operatorname{mod}\, i =0
2modi=0 的值,第二步我们翻转
i
=
2
i=2
i=2 即所有
2
2
2 的倍数,重复这个过程,容易发现仅当
μ
(
i
)
≠
0
\mu(i)\not=0
μ(i)=0 时需要翻转。
稍微解释一下算了,假设
i
i
i 不存在平方因子,即不存在整数
a
≥
2
a \geq 2
a≥2,满足
a
2
∣
i
a^2\ |\ i
a2 ∣ i,则
i
i
i 在算数基本定理拆解下指数全为
1
1
1,
i
=
∏
j
=
1
s
p
j
i=\prod_{j=1}^sp_j
i=∏j=1spj。当
s
=
1
s =1
s=1 时
i
i
i 显然需要翻转,因为第
i
i
i 个硬币是朝下的;当
s
=
2
s=2
s=2 时
i
i
i 同样也需要翻转,因为
i
=
p
1
、
p
2
i=p_1、p_2
i=p1、p2 翻转后,
p
1
p
2
p_1p_2
p1p2 依然朝下;当
s
=
3
s=3
s=3 时
i
=
p
1
、
p
2
、
p
3
、
p
1
p
2
、
p
1
p
3
、
p
2
p
3
i=p_1、p_2、p_3、p_1p_2、p_1p_3、p_2p_3
i=p1、p2、p3、p1p2、p1p3、p2p3 翻转,翻转
6
6
6 次
p
1
p
2
p
3
p_1p_2p_3
p1p2p3 朝下,需要翻转;也就是当
i
i
i 不存在平方因子在遍历到它前它会被翻转
∑
i
=
1
s
C
s
i
=
2
s
−
2
\sum_{i=1}^sC_s^i=2^s-2
∑i=1sCsi=2s−2 次为偶数次,所以它一定要被翻转,而当
i
i
i 存在平凡因子时,它会在前述基础上再翻上
∏
j
=
1
s
p
j
\prod_{j=1}^sp_j
∏j=1spj 即
2
s
−
1
2^s-1
2s−1 次为奇数次,故无需翻转。
于是我们可以进一步解读题意:
给定正整数
n
n
n,求
∑
i
=
0
n
μ
2
(
i
)
\displaystyle\sum_{i=0}^n \mu^2(i)
i=0∑nμ2(i)。
不过
n
n
n 很大,直接通过杜教筛求解无法通过全部评测用例,于是记
S
(
n
)
S(n)
S(n) 为
4
∼
n
4\sim n
4∼n 中包含平方因子的数的个数,则答案为
n
−
S
(
n
)
n - S(n)
n−S(n),考虑容斥有:
S
(
n
)
=
∑
k
=
2
⌊
n
⌋
(
−
1
)
k
∑
2
≤
d
1
<
d
2
<
⋯
<
d
k
−
1
≤
⌊
n
⌋
⌊
n
∏
d
i
2
⌋
S(n)=\sum_{k=2}^{\lfloor\sqrt n\rfloor} (-1)^k\sum_{2\leq d_1<d2<\cdots<d_{k-1}\leq\lfloor\sqrt n\rfloor} \left\lfloor\frac n{\prod d_i^2}\right\rfloor
S(n)=k=2∑⌊n⌋(−1)k2≤d1<d2<⋯<dk−1≤⌊n⌋∑⌊∏di2n⌋整理得:
S
(
n
)
=
∑
d
=
2
⌊
n
⌋
−
μ
(
d
)
⌊
n
d
2
⌋
S(n)=\sum_{d=2}^{\lfloor\sqrt n\rfloor}-\mu(d)\left\lfloor\frac n{d^2}\right\rfloor
S(n)=d=2∑⌊n⌋−μ(d)⌊d2n⌋答案可变式为:
n
−
S
(
n
)
=
1
⌊
n
1
2
⌋
−
S
(
n
)
=
∑
d
=
1
⌊
n
⌋
μ
(
d
)
⌊
n
d
2
⌋
n-S(n)=1\left\lfloor\frac n{1^2}\right\rfloor-S(n)=\sum_{d=1}^{\lfloor\sqrt n\rfloor}\mu(d)\left\lfloor\frac n{d^2}\right\rfloor
n−S(n)=1⌊12n⌋−S(n)=d=1∑⌊n⌋μ(d)⌊d2n⌋预处理
μ
\mu
μ 长
(
n
)
2
/
3
(\sqrt n)^{2/3}
(n)2/3 的前缀和,剩下的部分通过数论分块+杜教筛求解,
n
/
d
2
,
d
>
(
n
)
2
/
3
n/d^2,d>(\sqrt n)^{2/3}
n/d2,d>(n)2/3 一共有
n
1
/
3
n^{1/3}
n1/3 种不同的取值,在缓存了杜教筛的结果的情况下复杂度亚于
O
(
n
)
O(\sqrt n)
O(n),但亚多少这个均摊就很难算了。文章来源:https://www.toymoban.com/news/detail-745716.html
#include <stdio.h>
#include <unordered_map>
#include <math.h>
const int N = 1.5e7 + 10;
int p[N], mu[N + 1]{0, 1};
bool mark[N + 1];
std::unordered_map<int, long long> S;
long long djs(int n) {
if (n <= N) return mu[n];
if (S[n]) return S[n];
long long sum = 1;
for (long long l = 2, r; l <= n; l = r + 1) {
r = n / (n / l);
sum -= djs(n / l) * (r - l + 1);
}
return S[n] = sum;
}
int main() {
for (int k = 2, m = 0; k <= N; ++k) {
if (!mark[k])
p[m++] = k, mu[k] = -1;
for (int i = 0; i < m; ++i) {
long long kp = (long long) k * p[i];
if (kp >= N) break;
mark[kp] = 1;
if (k % p[i]) mu[kp] = -mu[k];
else break;
}
}
long long n, ans = 0;
scanf("%lld", &n);
long long l = 1, sn = sqrt(n + 0.5);
for (long long high = sn > N ? N : sn; l <= high; ++l) {
ans += mu[l] * (n / l / l);
mu[l] += mu[l - 1];
}
for (long long r; l <= sn; l = r + 1) {
long long ndd = n / l / l;
r = sqrt(n / ndd + 0.5);
ans += (djs(r) - djs(l - 1)) * ndd;
}
printf("%lld", ans);
}
不过初始化取
1
e
6
、
1
e
7
1e6、1e7
1e6、1e7 都有些测试点会超时,取到
1.5
e
7
1.5e7
1.5e7 才能通过全部用例。
图像上我也没看太懂,好像均摊下来复杂度能亚
O
(
n
1
3
)
O(n^{\frac 13})
O(n31),想不明白,内存往题目限制上开就完事了。文章来源地址https://www.toymoban.com/news/detail-745716.html
到了这里,关于第十四届蓝桥杯大赛软件赛省赛(C/C++ 大学A组)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!