0%

线性筛

线性筛的作用就是为了保证每个合数都只被其【最小的】那个质因数标记一次。

image

这下面👇讲的比较一般啊。

线性筛只有可能标记一个数字一次。

image

这个讲的有点复杂了,最后是关键,就是每个数只会以唯一方式被标记。

m=np1=p1m11×p2m2×p3m3××pkmkm=\frac{n}{p_1}=p_{1}^{m_1-1}\times p_{2}^{m_2}\times p_{3}^{m_3} \times \cdots \times p_{k}^{m_k}

n 只会在遍历到 m 时被标记。

假设这个n,在遍历到 m 之前就被标记,假设该数字是 m‘,但是 m×minp[m]=nm’\times \text{minp}[m']=n 是必须满足的条件,if(p==minp[i]) break; ,m‘ 只会标记到这个最小素因子*自身,因此 m’ 想标记 n,当且仅当 m×minp[m]=nm’\times \text{minp}[m']=n。显然,m‘=m,得证。

https://qoj.ac/submission/995868

https://cp-algorithms.com/algebra/prime-sieve-linear.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//  minp[i] 存储的是i的最小素因数
// https://www.luogu.com.cn/record/221554377
vector<ll> minp,primes;
void sieve(ll n){
minp.assign(n+5,0);
// minp[1]=1; // 加了这句话可能把 1 误判成素数,要小心
for(int i=2;i<=n;++i){
if(minp[i]==0){ // 是素数
minp[i]=i;
primes.push_back(i);
}
for(auto p:primes){
if(i*p>n){ // 超范围了
break;
}
minp[i*p]=p;
// 为了保证每个合数都只被其【最小的】那个质因数标记一次 break
// i * p_next = (p * k) * p_next 最小的应该是p
if(p==minp[i]){
break;
}
}
}
}

使用线性筛的minp分解素因数更快,效率更高。

1
2
3
4
5
6
7
vector<ll> prs;
for(int j=W[i];j>1;j/=minp[j]){ // 素因数分解
ll p=minp[j];
prs.pb(p);
}
sort(all(prs));
prs.resize(unique(all(prs))-prs.begin());

题目大意

题目描述
你有一个包含数字 0099 的键盘。给定一个正整数 mm,你的目标是使用键盘打出一个正整数,使其为 mm 的倍数。

但是,键盘上有 kk正整数按键损坏了(数字 00 的按键一定完好,只有 1199 中的部分按键可能损坏)。你只能使用剩下的完好按键来打出这个数字。

输入格式
第一行包含一个整数 TT1T5×1041 \le T \le 5 \times 10^4),表示测试数据的组数。

对于每组测试数据:
第一行包含两个整数 mmkk1m1071 \le m \le 10^70k90 \le k \le 9),表示目标数字必须是 mm 的倍数,且有 kk 个按键损坏。

用其他剩余按键,敲出 m 的倍数。

第二行包含 kk 个互不相同的整数 pip_i1pi91 \le p_i \le 9),具体表示哪些数字按键被损坏。

输出格式
对于每组测试数据:
如果存在合法的构造方案,请以“连续按下某数字 aabb 次”的操作序列形式输出:
第一行输出一个整数 nn1n1001 \le n \le 100),表示操作的数量。
接下来 nn 行,每行输出两个整数 aabb0a90 \le a \le 91b10181 \le b \le 10^{18}),表示按下数字 aabb 次。

如果不存在任何合法的方案,仅输出一行 -1

样例

1
2
3
4
5
6
7
8
9
10
11
12
输入:
2
37 7
2 3 5 6 7 8 9
7 9
1 2 3 4 5 6 7 8 9

输出:
2
1 3
0 1
-1

样例解释
对于第一组样例,给出的操作为:按下数字 1133 次,然后按下数字 3311 次。这样按出来的正整数是 11101110。由于 1110=37×301110 = 37 \times 30,它是 3737 的倍数,并且只使用了未损坏的按键 1100,符合题目要求。

对于第二组样例,数字 1199 的按键全部损坏,唯一能打出的是由数字 00 组成的数(值为 00)。但题目要求打出的必须是一个正整数,因此无法完成任务,输出 -1

思路讲解

呃,首先我们还是一步一步来吧,就是首先,我们可以想到,就是 m 当中的这个因数还是越少越好嘛,想办法先消掉一些

下面这一步也是比较自然的,因为注意到,0 是永远都在的

我们可以把这个 m 当中的因数 2,5 给除掉(因为如果原数字不满足,就加 0 即可),得到的新 m 和这个 10 互质(反正应该就是想办法让原数和 10 互质,可以通过不断除以 10,记录一下除 10 的这个个数,最后 +0 即可)。

然后,就可以想到欧拉定理(其实我们的构造目标就是一个同余式,我们得到的任何东西,也要想办法化为同余式):

这是 Euler 定理(欧拉定理)。

对于任意正整数 mm 和整数 aa,若 gcd(a,m)=1\gcd(a, m) = 1,则

aφ(m)1(modm)a^{\varphi(m)} \equiv 1 \pmod{m}

image

不难得出如下结论:

image

我们现在是构出来了,但是万一 9 被扣了我们不就炸了?

我们发现,只要我们能够勾出来一个全 1 的,我们能够非常自然的勾出全 d 的。

image

这个最后一步,也挺难的,反正我确实不大能够推出来。使用了变量直接代换的这个技巧,把一个 9 带到了这个整除符号的两边。

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ll cnt2=0;
while (M%2==0) {
M/=2;
cnt2++;
}
ll cnt5=0;
while (M%5==0) {
M/=5;
cnt5++;
}
ll phi=euler_phi(M);
ll cnt0=max(cnt2,cnt5);
if (cnt0==0) {
cout<<1<<"\n";
}else {
cout<<2<<"\n";
}
cout<<*keys.rbegin()<<" "<<9*phi<<"\n";
if (cnt0!=0) {
cout<<0<<" "<<cnt0<<"\n";
}

AC代码

AC
https://codeforces.com/gym/106380/submission/366492238

心路历程(WA,TLE,MLE……)3737111b10181 \le b \le 10^{18}

基本情况

乱打的,比较狗屎了。

image

心得感悟

感觉还是要做中国的省赛,感觉中国的题目和国外的题目还是风格有所不同吧。

题目 I take from the richer

就是这个排序一遍以后直接模拟。

不难发现,肯定是先选小的数,因为先选大的数,小的数就没法贡献了

这种一定要小心这个加上一个负数,要用这个 max 0ll 去兜底。

image

https://codeforces.com/gym/106380/submission/366235168

题目 Just reseat!

模拟题,不多说了。

https://codeforces.com/gym/106380/submission/366229614

题目 Do you play Ballance?

直接分类讨论。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
vector<A_b_c> A(N+2);
vector<ll> cnt(10);
for (int i=1;i<=N;++i) {
ll a,b,c;
cin>>a>>b>>c;
A[i]={a,b,c};
if (A[i]==A_b_c{1,0,0}) {
cnt[1]++;
}else if (A[i]==A_b_c{0,1,0}) {
cnt[2]++;
}else if (A[i]==A_b_c{0,0,1}){
cnt[3]++;
}
}
if (cnt[1] && cnt[2] && cnt[3]) {
cout<<3<<"\n";
return;
}
for (int i=1;i<=N;++i) {
if (A[i]==A_b_c{1,0,0}) {

}else if (A[i]==A_b_c{0,1,0}) {

}else if (A[i]==A_b_c{0,0,1}){

}else {
if (A[i].a && cnt[1]) {
++cnt[1];
}else if (A[i].b && cnt[2]) {
++cnt[2];
}else if (A[i].c && cnt[3]) {
++cnt[3];
}else if (A[i]==A_b_c{1,1,0}) {
++cnt[4];
}else if (A[i]==A_b_c{1,0,1}) {
++cnt[5];
}else if (A[i]==A_b_c{0,1,1}) {
++cnt[6];
}else if (A[i]==A_b_c{1,1,1}) {
++cnt[7];
}
}
}
ll ans1=ll(bool(cnt[1]))+ll(bool(cnt[2]))+ll(bool(cnt[3]));
ll ans2=ll(bool(cnt[4]))+ll(bool(cnt[5]))+ll(bool(cnt[6]));
ll ans=ans1;
if (ans1) {
if (ans2) {
++ans;
}
}else {
if (ans2<=2) {
ans=1;
}else {
ans=2;
}
}
if (ans==0 && cnt[7]) {
++ans;
}
cout<<ans<<"\n";

https://codeforces.com/gym/106380/submission/366228036

题目大意

题目描述
Capoo 的初始 rating 为 x=0x=0
现在有 nn 场比赛,对于第 ii 场比赛,它的难度为 aia_i,隐藏分数为 bib_i。每场比赛最多只能参加一次。
只有当当前 rating xaix \le a_i 时,Capoo 才能参加第 ii 场比赛。参加完该场比赛后,Capoo 的 rating 将会更新为 max(bi,x)\max(b_i, x)
你可以自由安排参加这 nn 场比赛的顺序,求 Capoo 最多能参加多少场比赛。

输入格式
第一行包含一个整数 nn1n1061 \le n \le 10^6)。
接下来的 nn 行,每行包含两个整数 aia_ibib_i0ai,bi10180 \le a_i, b_i \le 10^{18}),分别表示第 ii 场比赛的难度和隐藏分数。

输出格式
输出一行一个整数,表示 Capoo 最多能参加的比赛场数。

样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
输入:
10
19 18
6 15
5 8
4 20
18 3
16 9
0 7
5 17
2 13
15 17

输出:
5

样例解释

注意,这个人比较喜欢挑战这个难题。
一种最多参加 55 场比赛的合法顺序如下:

  1. 初始时 x=0x = 0

  2. 参加第 77 场比赛(a7=0,b7=7a_7=0, b_7=7):满足 xa7x \le a_7000 \le 0),参加后 xx 更新为 max(0,7)=7\max(0, 7) = 7

  3. 参加第 55 场比赛(a5=18,b5=3a_5=18, b_5=3):满足 xa5x \le a_57187 \le 18),参加后 xx 更新为 max(7,3)=7\max(7, 3) = 7

  4. 参加第 66 场比赛(a6=16,b6=9a_6=16, b_6=9):满足 xa6x \le a_67167 \le 16),参加后 xx 更新为 max(7,9)=9\max(7, 9) = 9

  5. 参加第 1010 场比赛(a10=15,b10=17a_{10}=15, b_{10}=17):满足 xa10x \le a_{10}9159 \le 15),参加后 xx 更新为 max(9,17)=17\max(9, 17) = 17

  6. 参加第 11 场比赛(a1=19,b1=18a_1=19, b_1=18):满足 xa1x \le a_1171917 \le 19),参加后 xx 更新为 max(17,18)=18\max(17, 18) = 18
    总计参加了 55 场比赛,可以证明这是最多能参加的比赛场数。

思路讲解

将所有比赛分成两组:

  • 安全组 Abiaib_i \le a_i。参加后 xmax(x,bi)aix \leftarrow \max(x, b_i) \le a_i,也就是说参加这场比赛不会把 xx 抬到超过它自身的门槛 aia_i。对后续的影响很温和。

  • 危险组 Bbi>aib_i > a_i。参加后 xx 必然跳到 bib_i(因为 bi>aixb_i > a_i \ge x),对后续影响大。

为什么这个分组有用?因为安全组内部,如果按 aia_i 升序排列,全部都能参加——每次参加后 xaix \le a_i,而下一场的 aa 更大,必然满足条件。所以安全组是"稳赚不赔"的。

下一步应该关注:安全组全做了,危险组怎么尽可能多地塞进来**?(把安全组给固定住)**

image

第三层:排序与交错——两组的最优合并策略

  • 安全组按 aia_i 升序排。

  • 危险组按 bib_i 升序排(参加后 xx 跳到 bib_i,越小越好,留给后面的空间越大)。

一下的这个规则就是,在上面情况下,B 不会影响 A?(先不要去想 B 会不会生效

关键的交错规则:在做每一个危险组比赛 BjB_jbb 值为 bjb_j)之前,先把安全组中所有 ai<bja_i < b_j 的比赛做完。

为什么?做完 BjB_jx=bjx = b_j,那些 a<bja < b_j 的安全组比赛就再也做不了了。提前做掉它们不会抬高 xx(因为安全组的 ba<bjb \le a < b_j),不影响 BjB_j 的可行性。

这样,安全组的比赛一场都不会浪费——它们总是在 xx 被抬高之前被消化掉。最终的答案就是:

安全组全部+按上述交错策略能做的危险组数量\text{安全组全部} + \text{按上述交错策略能做的危险组数量}

不难想到,危险组按照这个 b 从小到大排比较好,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
auto forward=[&]() -> bool {
while (idx1<SZ(safeA) && idx2<SZ(dangerA) && safeA[idx1].a<dangerA[idx2].b) {
cur_ability=max(cur_ability,safeA[idx1].b);
++idx1;
}

if (cur_ability<=dangerA[idx2].a) {
cur_ability=max(cur_ability,dangerA[idx2].b);
++ans;
}
++idx2;
if (idx2<SZ(dangerA)) {
return true;
}
return false;
};
while (forward()) {
}

AC代码

心路历程(WA,TLE,MLE……)

题目大意

题目描述
给定一个长度为 nn 的正整数数组,求有多少个非空连续子数组满足以下条件:子数组内所有元素的和,能够被该子数组中所有元素包含的数字字符的最大值整除。

(注:数字字符指组成数字的 090 \sim 9。例如,数组 [2025, 11, 15] 中包含的数字字符有 0,1,2,50, 1, 2, 5,其最大值为 55。)

输入格式
第一行包含一个整数 TT1T1041 \le T \le 10^4),表示测试用例的数量。
对于每个测试用例:
第一行包含一个整数 nn1n1051 \le n \le 10^5),表示数组长度。
第二行包含 nn 个正整数 x1,x2,,xnx_1, x_2, \dots, x_n1xi1091 \le x_i \le 10^9),表示数组元素。
保证所有测试用例中 nn 的总和不超过 10510^5

输出格式
对于每个测试用例,输出一行一个整数,表示满足条件的非空子数组数量。

样例输入

1
2
3
4
5
2
3
213 12 21
7
314 880 246 170 493 474 129

样例输出

1
2
4
7

样例解释
以第一个测试用例 [213, 12, 21] 为例,考察其所有非空子数组:

  • [213]:包含数字 1,2,31, 2, 3,最大数字为 33。子数组元素和为 213213213mod3=0213 \bmod 3 = 0满足条件

  • [12]:包含数字 1,21, 2,最大数字为 22。子数组元素和为 121212mod2=012 \bmod 2 = 0满足条件

  • [21]:包含数字 1,21, 2,最大数字为 22。子数组元素和为 212121mod2021 \bmod 2 \neq 0,不满足条件。

  • [213, 12]:包含数字 1,2,31, 2, 3,最大数字为 33。子数组元素和为 213+12=225213 + 12 = 225225mod3=0225 \bmod 3 = 0满足条件

  • [12, 21]:包含数字 1,21, 2,最大数字为 22。子数组元素和为 12+21=3312 + 21 = 3333mod2033 \bmod 2 \neq 0,不满足条件。

  • [213, 12, 21]:包含数字 1,2,31, 2, 3,最大数字为 33。子数组元素和为 213+12+21=246213 + 12 + 21 = 246246mod3=0246 \bmod 3 = 0满足条件

满足条件的子数组共有 4 个。

思路讲解

≤d 比=d 好计数得多,那为什么不使用 f(≤d)-f(≤d-1) 得到 f(=d) 呢?

不过,要注意到,就是光一个 d 是不够描述这个 f 函数的。

定义 $ f(≤d,big_d)$ 指的是最大 digit 是 d≤d 而且可以被 bigdbig_d 整除的这个子段数量。

因此,不难发现 $ f(=d,big_d)=f(≤d,big_d)-f(≤d-1,big_d)$。

使用这一容斥方法解决该问题,是因为我们发现在编程中,我们发现,很难对 最大 digit=d 进行计数(你要是不信的话可以自此试试)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
for (int big_d=1;big_d<=9;++big_d) {
vector<ll> F(10+2);
for (int d=big_d-1;d<=big_d;++d) {
vector<ll> R(big_d);
for (int i=1;i<=N;++i) {
if (mx_d[i]>d) {
// if (find_mx) {
// ans+=R[0];
// }
R.assign(big_d,0);
continue;
}
// cal(A[i]);
ll rem=A[i]%big_d;
rotate(R.rbegin(),R.rbegin()+rem,R.rend());
R[rem]++;
F[d]+=R[0];

// if (find_mx) {
// ans+=R[0];
// }
}
}
ans+=F[big_d]-F[big_d-1];
}

AC代码

AC
https://codeforces.com/gym/106380/submission/366441214

心路历程(WA,TLE,MLE……)