文章目录
2021/12/19 训练补题题解
1. Fight Against Traffic
-
知识点:最短路
-
题目:无权简单图中加一条边避免起点 s s s 到终点 d d d 的最短路缩短,问合法方案数量。
-
思路:若在点 a , b a,b a,b 之间加一条边 g ′ g' g′ 可使最短路缩短,那更新的最短路一定是经过 g ′ g' g′ 边的。于是可以算出所有点到起点终点两点 s , d s,d s,d 的最短路( d i j k s t r a dijkstra dijkstra),遍历尝试添加所有可加边,检查是否是合法的(
min(startdist[i], startdist[j]) + 1 + min(enddist[i], enddist[j])
)。#include<bits/stdc++.h> #define rep(i, a, b) for(int i = a; i <= b; i++) #define per(i, a, b) for(int i = a; i >= b; i--) const int INF = 0x7fffffff; #define ll long long #define PII pair<int, int> const int N =1e3 + 5; using namespace std; int n, s, d, m, k; ll g[N][N], ans; ll vis[N], startdist[N], enddist[N]; vector<PII> vec[N]; void init(){ memset(g, 0, sizeof(g)); rep(i, 1, n){ vec[i].clear(); startdist[i] = enddist[i] = INF; } } void dijkstra(int s, ll dist[]){ memset(vis, 0, sizeof(vis)); priority_queue<PII, vector<PII>, greater<PII> > heap; dist[s] = 0, heap.push({0, s}); while(heap.size()){ PII now = heap.top(); heap.pop(); int nod = now.second; if(vis[nod]) continue; vis[nod] = 1; for(auto j : vec[nod]){ int To = j.first, distmp = j.second; if(dist[To] > now.first + distmp){ dist[To] = now.first + distmp; heap.push({dist[To], To}); } } } return; } int main(){ cin >> n >> m >> s >> d; init(); rep(i, 1, m){ int u, v; cin >> u >> v; g[u][v] = g[v][u] = 1; vec[u].push_back({v, 1}), vec[v].push_back({u, 1}); } dijkstra(s, startdist); dijkstra(d, enddist); ans = 0; rep(i, 1, n) rep(j, i + 1, n){ if(g[i][j]) continue; if(min(startdist[i], startdist[j]) + 1 + min(enddist[i], enddist[j]) >= startdist[d]) ans++; } cout << ans << endl; } /* 5 4 1 5 1 2 2 3 3 4 4 5 */
3. New Year and Rating
-
知识点:贪心
-
思路:我们发现参赛者每多参加一场比赛,都会在参赛前无形中添加一条其等级的约束(参加 A A A 组比赛说明其等级不得小于 1900 1900 1900;参加 B B B 组比赛说明其等级不得大于 1899 1899 1899 )
- 于是我们通过每场比赛不断精确其等级范围即可,注意当范围确定不满足参赛要求时,说明情况无解。
#include<bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double #define VI vector<int> #define PII pair<int, int> const db Pi = 3.141592653589793; const int INF = 0x7fffffff; const int N = 1e5 + 5; const db eps = 1e-10; int n, c, d, have2; ll minn, maxn; int main(){ cin >> n; minn = -INF, maxn = INF, have2 = 0; rep(i, 1, n){ cin >> c >> d; if(d == 1){ if(maxn < 1900){ puts("Impossible"); return 0; } if(minn < 1900) minn = 1900; //更新 minn += c, maxn += c; } else if(d == 2){ have2 = 1; if(minn >= 1900){ puts("Impossible"); return 0; } if(maxn > 1899) maxn = 1899; //更新 maxn += c, minn += c; } } if(!have2) puts("Infinity"); else cout << maxn << endl; } /* 3 -7 1 5 2 8 2 */
4. Sasha and a Bit of Relax
-
思路:假设一个区间 [ a x , a y ] [a_x,a_y] [ax,ay] 满足要求,则按照要求有
a x ⊕ a x + 1 ⊕ . . . ⊕ a y = ( a x ⊕ . . . ⊕ a m i d ) ⊕ ( a m i d + 1 ⊕ . . . ⊕ a y ) = x ⊕ x = 0 a_x\oplus a_{x+1}\oplus ...\oplus a_y=(a_x\oplus ...\oplus a_{mid})\oplus (a_{mid+1}\oplus ...\oplus a_y)=x\oplus x=0 ax⊕ax+1⊕...⊕ay=(ax⊕...⊕amid)⊕(amid+1⊕...⊕ay)=x⊕x=0
于是若 y − x + 1 y-x+1 y−x+1 为偶数且 a x ⊕ a x + 1 ⊕ . . . ⊕ a y = 0 a_x\oplus a_{x+1}\oplus ...\oplus a_y=0 ax⊕ax+1⊕...⊕ay=0 ,则其此区间一定满足要求。证明:可以按位拆开考虑,当一位被区间内所有元素异或后为 0 0 0,表示一定有偶数个元素在这一位的位置为 1 1 1。将这偶数个元素分成两两一对,那每一对 1 1 1 可以共同在区间的前后两部分,或者分别在区间的前后两部分。这两种分发都导致这一对 1 1 1 使得区间两部分的异或和在这一位贡献相同,故前后两部分元素的异或和一定相同。
- 于是计算有多少个长度为偶数的区间异或和为
0
0
0 即可。由于
n
n
n 太大,不可能用前缀异或和来
O
(
n
2
)
O(n^2)
O(n2) 的计算,于是可以将在奇/偶数位的相同前缀异或值存在一起,存在同一部分中的位置之间的区间均满足条件。注意
VI Xor[3][1 << 21]
不要开小了。
#include<bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double #define VI vector<int> #define PII pair<int, int> const db Pi = 3.141592653589793; const int INF = 0x7fffffff; const int N = 3e5 + 5; const int NN = (1 << 21); const db eps = 1e-10; int n, a[N], sum[N], vis[NN]; ll ans; VI Xor[3][NN]; int main(){ scanf("%d", &n); memset(vis, 0, sizeof(vis)); rep(i, 1, n){ scanf("%d", &a[i]); sum[i] = (i == 1 ? a[i] : sum[i - 1] ^ a[i]); Xor[(i % 2) ? 1 : 0][sum[i]].push_back(i); vis[sum[i]] = 1; } Xor[0][0].push_back(0); ans = 0; rep(i, 0, NN - 1){ if(!vis[i]) continue; rep(k, 0, 1){ VI tmp = Xor[k][i]; if(!tmp.size()) continue; ans += (ll)tmp.size() * ((ll)tmp.size() - 1) / 2; //直接算 } } cout << ans << endl; } /* 5 1 2 3 4 5 */
- 于是计算有多少个长度为偶数的区间异或和为
0
0
0 即可。由于
n
n
n 太大,不可能用前缀异或和来
O
(
n
2
)
O(n^2)
O(n2) 的计算,于是可以将在奇/偶数位的相同前缀异或值存在一起,存在同一部分中的位置之间的区间均满足条件。注意
5. Select Half
-
知识点:动态规划
-
思路:分为奇偶两种情况讨论。设 f [ i ] f[i] f[i] 表示前 i i i 个数字中选择 ⌊ i 2 ⌋ \lfloor\dfrac{i}{2}\rfloor ⌊2i⌋ 个两两不相邻数字的最大可能和,来线性动态规划求 f [ n ] f[n] f[n]
- 首先 f [ 1 ] = 0 f[1]=0 f[1]=0 因为只能选 0 0 0 个数。正常来说,计算 f [ i ] f[i] f[i] 只需要考虑选或不选 a [ i ] a[i] a[i] 即可。
- 但当
i
i
i 为偶数时,若不选
a
[
i
]
a[i]
a[i] 则无论最大可能和一定要选
1
,
3
,
5
,
.
.
.
,
i
−
1
1,3,5,...,i-1
1,3,5,...,i−1 等数字,于是有
f[i] = max(f[i - 2] + a[i], sum[i - 1])
.
#include<bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double #define VI vector<int> #define PII pair<int, int> const db Pi = 3.141592653589793; const int INF = 0x7fffffff; const int N = 2e5 + 5; const db eps = 1e-10; ll n, a[N], sum[N], f[N]; int main(){ cin >> n; rep(i, 1, n){ cin >> a[i]; sum[i] = a[i] + (i > 2 ? sum[i - 2] : 0); } f[1] = 0; //不能写 max(0ll, a[1]), 因为 1 / 2 = 0 rep(i, 2, n){ if(i % 2) f[i] = max(f[i - 2] + a[i], f[i - 1]); else f[i] = max(f[i - 2] + a[i], sum[i - 1]); //偶数时一定要考虑 sum[i - 1] 的情况 } cout << f[n] << endl; } /* 27 18 -28 18 28 -45 90 -45 23 -53 60 28 -74 -71 35 -26 -62 49 -77 57 24 -70 -93 69 -99 59 57 -49 */
6. Equals
-
知识点:并查集
-
思路:重点可以任意(无限)次交换操作,(自己试试)发现若一堆位置互相相连(可交换),则每个数字一定可以换到同一堆的任意一个位置,则若 a [ i ] a[i] a[i] 与 i i i 相连,则 a [ i ] a[i] a[i] 一定可以换到 a [ i ] a[i] a[i] 对应数值的位置。
- 于是在一个集合中便可以换到对应位置,用并查集将所有交换对划到集合中,最后检查对应数对即可。
//可以任意次交换,于是在一团中的一定可以换到想去的位置 #include<bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double #define VI vector<int> #define PII pair<int, int> const db Pi = 3.141592653589793; const int INF = 0x7fffffff; const int N = 1e5 + 5; const db eps = 1e-10; int n, m, a[N], pre[N]; ll ans = 0; int find(int x){ if(pre[x] == x) return x; return pre[x] = find(pre[x]); } void join(int x,int y){ int fx = find(x), fy = find(y); if(fx != fy) pre[fx] = fy; } int main(){ cin >> n >> m; rep(i, 1, n){ cin >> a[i]; pre[i] = i; } rep(i, 1, m){ int x, y; cin >> x >> y; join(x, y); } rep(i, 1, n) if(find(i) == find(a[i])) ans++; cout << ans << endl; } /* 5 2 5 3 1 4 2 1 3 5 4 */
8. Trailing Loves (or L’oeufs?)
-
知识点:数学(素数)
-
题目:求十进制 n n n 的阶乘 n ! n! n! 在 m m m 进制表示法后面有多少零。
-
思路: n 10 ! n_{10}! n10! 在 m m m 进制下有 t t t 个零表示 m t ∣ n ! 且 m t + 1 ∤ n ! \color{red}m^t|n! \;且\; m^{t+1}\not |n! mt∣n!且mt+1∣n!
- 于是将 m m m 质因数分解 m = p 1 c 1 p 2 c 2 p 3 c 3 . . . m=p_1^{c_1}p_2^{c_2}p_3^{c_3}... m=p1c1p2c2p3c3...,后我们对于每个质因数 p i p_i pi 计算 r e a l i = n ! / p i real_i=n!/p_i reali=n!/pi,故 t i = r e a l i c i \color{red}t_i=\dfrac{real_i}{c_i} ti=cireali,对于不同质因数 p i p_i pi,最终的 t t t 一定是 min { t i } \min\{t_i\} min{ti}
- 如何 O ( log n ) O(\log n) O(logn) 计算 r e a l i = n ! / p i real_i=n!/p_i reali=n!/pi:发现 n ! n! n! 中质数因子 p p p 的个数就是 1 ∼ n 1\sim n 1∼n 中每个数含有的质因数 p p p 个数之和。那么我们发现 1 ∼ n 1\sim n 1∼n 中, p p p 的倍数,即至少有一个质因子 p p p 的数显然有 ⌊ n p ⌋ \lfloor\dfrac{n}{p} \rfloor ⌊pn⌋ 个,而 p 2 p^2 p2 的倍数,即至少有两个质因子 p p p 的数显然是有 ⌊ n p 2 ⌋ \lfloor\dfrac{n}{p^2} \rfloor ⌊p2n⌋ 不过由于这两个质因子 p p p 中有一个已经在 ⌊ n p ⌋ \lfloor\dfrac{n}{p} \rfloor ⌊pn⌋ 统计过了,所以只需要再统计第二个质因子,也就是直接累加 ⌊ n p 2 ⌋ \lfloor\dfrac{n}{p^2} \rfloor ⌊p2n⌋ 而不是累乘。即 ⌊ n p ⌋ + ⌊ n p 2 ⌋ + . . . + ⌊ n p log p n ⌋ = ∑ p k ≤ n ⌊ n p k ⌋ \color{red}\lfloor\dfrac{n}{p} \rfloor+\lfloor\dfrac{n}{p^2} \rfloor+...+\lfloor\dfrac{n}{p^{\log_p n}} \rfloor=\sum\limits_{p^k\le n}\lfloor\dfrac{n}{p^k} \rfloor ⌊pn⌋+⌊p2n⌋+...+⌊plogpnn⌋=pk≤n∑⌊pkn⌋
- 因为 m ≤ 1 e 12 m\le 1e12 m≤1e12, 2 ∗ 3 ∗ 5 ∗ 7 ∗ 11 ∗ 13 ∗ 17 ∗ 19 ∗ 23 ∗ 29 ∗ 31 ∗ 37 = 7 , 420 , 738 , 134 , 810 > m 2*3*5*7*11*13*17*19*23*29*31*37=7,420,738,134,810>m 2∗3∗5∗7∗11∗13∗17∗19∗23∗29∗31∗37=7,420,738,134,810>m,故 m m m 最多有 11 11 11 个不同质因子,复杂度不会太高。
#include<bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = (a); i <= (b); i++) #define per(i, a, b) for(int i = (a); i >= (b); i--) #define ll long long #define db double #define VI vector<int> const int N = 1e5 + 5; ll n, m, cnt = 0, ans, tmp; struct AC{ ll p, num, real; }a[N]; int main(){ cin >> n >> m; ll y = m; rep(i, 2, sqrt(m)){ if(y % i == 0){ tmp = 0; while(y % i == 0) y /= i, tmp++; a[++cnt].p = i, a[cnt].num = tmp; } } if(y != 1) a[++cnt].p = y, a[cnt].num = 1; rep(i, 1, cnt){ tmp = 0; for(ll j = n; j; j /= a[i].p) tmp += j / a[i].p; //log(n) 计算 n! 中有多少个 a[i].p a[i].real = tmp; } ans = (1ll << 62); rep(i, 1, cnt) ans = min(ans, a[i].real / a[i].num); cout << ans << endl; } /* 6 9 */