【题解】Educational Codeforces Round 142(CF1792)
没有手速,再加上被 E 卡了,废掉了。
A.GamingForces
题目描述:
Monocarp 正在玩电脑游戏。他打算杀死 n n n 个怪兽,第 i i i 个的血量为 h i h_i h i 。
Monocarp 的角色有两个魔法咒语如下,都可以以任意顺序用任意次(可以不用),每次使用相当于一次操作。
选择两个怪兽并各 扣一滴血。
选择一个怪兽并且直接杀死。
当一个怪兽血量为 0 0 0 时,他死了 。
求杀死所有怪兽的最少操作次数。
题目分析:
只有两个怪兽同为 1 1 1 点血,这个时候我们通过 1 1 1 操作直接把它们弄死才会比直接通过操作 2 2 2 直接弄死更优。
所以判一下这个就好了。
代码:
点击查看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include<bits/stdc++.h> using namespace std; const int N = 10005; int a[N]; int main(){ int T;scanf("%d",&T); while(T--){ int n;scanf("%d",&n); for(int i=1; i<=n; i++) scanf("%d",&a[i]); int cnt = 0; for(int i=1; i<=n; i++){ if(a[i] == 1) ++cnt; } printf("%d\n",n - cnt / 2); } return 0; }
题目描述:
Eve 是个单口相声新手。她的第一场表演聚集了总计 2 2 2 个观众:Alice 和 Bob。
Eve 准备了 a 1 + a 2 + a 3 + a 4 a_1+a_2+a_3+a_4 a 1 + a 2 + a 3 + a 4 个相声表演节目。a i a_i a i 表示第 i i i 类相声的数目,每类的的特征如下:
Alice 和 Bob 都喜欢这类相声。
Alice 喜欢,Bob 不喜欢。
Bob 喜欢,Alice 不喜欢。
Alice 和 Bob 都不喜欢这类相声。
一开始,两位观众的心情都为 0 0 0 。
当一位观众听到他喜欢的相声表演时心情会加 1 1 1 ,当听到的是自己不喜欢的相声时,心情减 1 。
当某位观众心情严格小于 0 0 0 时,这位观众会离场。只要有一位 这样的观众离场,Eve 会特别伤心并且结束整个表演 。若演完了所有节目,也会结束表演。
求某种安排表演顺序的方式,使得 Eve 在结束表演前能表演的节目最多 。输出最多能表演的节目数。
译者注:若演完某个节目有观众退场,这个节目也算在总数之中 。
0 ≤ a 1 , a 2 , a 3 , a 4 ≤ 1 0 8 0 \le a_1,a_2,a_3,a_4 \le 10^8 0 ≤ a 1 , a 2 , a 3 , a 4 ≤ 1 0 8 。
题目分析:
好像是个大细节题,但是我一遍过就很爽[狂笑]
考虑最优的一个操作顺序肯定是:1 → 2 , 3 → 4 1 \to 2,3 \to 4 1 → 2 , 3 → 4 。
关键是中间 2 , 3 2,3 2 , 3 造成的贡献比较难算,可以显然发现我们先操作一次 2 2 2 后操作一次 3 3 3 它们的代价就抵消了,而且会多出两次节目,但是要注意如果 1 1 1 操作数量为 0 0 0 就不能相互抵消了。
所以就按照:操作 1 1 1 、2 , 3 2,3 2 , 3 抵消、操作 2 , 3 2,3 2 , 3 、操作 4 4 4 ,这样的顺序模拟一下即可。
代码:
点击查看代码
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 #include<bits/stdc++.h> #define int long long using namespace std; int cnt[10]; signed main(){ // freopen("in.txt","r",stdin); // freopen("out.txt","w",stdout); int T;scanf("%lld",&T); while(T--){ for(int i=1; i<=4; i++) scanf("%lld",&cnt[i]); int ans = 0; int a = cnt[1],b = cnt[1];ans += cnt[1]; if(a == 0){ if(cnt[2] + cnt[3] + cnt[4] > 0) printf("1\n"); else printf("0\n"); continue; } int tmp = min(cnt[2],cnt[3]);ans += 2 * tmp; cnt[2] -= tmp,cnt[3] -= tmp; if(cnt[2] > 0){ tmp = min(cnt[2],b+1);ans += tmp; a += tmp,b -= tmp; } if(cnt[3] > 0){ tmp = min(cnt[3],a+1);ans += tmp; a -= tmp,b += tmp; } if(a >= 0 && b >= 0){ ans += min(min(a+1,b+1),cnt[4]); } printf("%lld\n",ans); } return 0; }
C.Min Max Sort
题目描述:
对于一个排列,定义一次操作为:在排列中任选两个数字,将它们中的最大值插入至队尾,最小值插入至队首。
现在给定多个排列,问每个排列最少各需多少次操作才能变得严格递增。
1 ≤ n ≤ 2 × 1 0 5 1 \le n\le 2\times 10^5 1 ≤ n ≤ 2 × 1 0 5
题目分析:
如果我们要进行操作,那么最后 1 1 1 操作必然是 1 , n 1,n 1 , n ,然后倒推一下就会发现倒数第 2 2 2 次操作必然是 2 , n − 1 2,n-1 2 , n − 1 ,然后就可以发现倒数第 i i i 次操作必然是 i , n − i + 1 i,n-i+1 i , n − i + 1 。
所以假设我们通过 k k k 次操作可以使排列严格递增,也就是说值在 [ k + 1 , n − k ] [k+1,n-k] [ k + 1 , n − k ] 是不用我们管的了,已经符合条件了,将两头的 k k k 个进行排完序就结束了。
这个“不用管”用形式化的语言说其实就是,设 p o s i pos_i p o s i 表示数 i i i 出现的位置,则 p o s k + 1 < p o s k + 2 < ⋯ < p o s n − k pos_{k+1} < pos_{k+2} < \cdots < pos_{n-k} p o s k + 1 < p o s k + 2 < ⋯ < p o s n − k 。
所以可以直接一个个枚举 k k k 判断是否合法即可,也就是将 k k k 从 ⌊ n 2 ⌋ \lfloor \frac{n}{2} \rfloor ⌊ 2 n ⌋ 开始依次减 1 1 1 ,然后判断条件是否满足。
代码:
点击查看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include<bits/stdc++.h> using namespace std; const int N = 2e5+5; int a[N],p[N]; int main(){ int T;scanf("%d",&T); while(T--){ int n;scanf("%d",&n); for(int i=1; i<=n; i++) scanf("%d",&a[i]),p[a[i]] = i; int k = n / 2; while(k && p[k] < p[k+1] && p[n-k] < p[n-k+1]) --k; printf("%d\n",k); } return 0; }
D.Fixed Prefix Permutations
题目描述:
对于一个排列 p p p ,定义其美丽度 k k k 为
p 1 = 1 , p 2 = 2 , ⋯ , p k = k , p k + 1 ≠ k + 1 p_1=1,p_2=2,\cdots,p_k=k,p_{k+1} \neq k+1 p 1 = 1 , p 2 = 2 , ⋯ , p k = k , p k + 1 = k + 1
若 p , q p,q p , q 均为长度为 n n n 的排列,定义排列的运算 p ⋅ q p \cdot q p ⋅ q 为:
p ⋅ q = r p \cdot q = r p ⋅ q = r ,p , q , r p,q,r p , q , r 均为长度为 n n n 的排列
r j = q p j r_j = q_{p_j} r j = q p j
现在给出 n n n 个长度为 m m m 的排列,对于每一个 a i a_i a i ,求 a i ⋅ a j ( 1 ≤ j ≤ n ) a_i \cdot a_j(1 \le j \le n) a i ⋅ a j ( 1 ≤ j ≤ n ) 美丽值最大为多少,允许有 i = j i=j i = j 。
多测,T ≤ 1 0 4 T \le 10^4 T ≤ 1 0 4 ,∑ n ≤ 5 × 1 0 4 \sum n \le 5 \times 10^4 ∑ n ≤ 5 × 1 0 4 ,m ≤ 10 m \le 10 m ≤ 1 0
题目分析:
考虑如果就是给定了两个排列 p , q p,q p , q 怎么算它的美丽值。
要让 i i i 经过两个排列的置换后依旧是在 i i i 的位置,其实就是说设 p o s i pos_i p o s i 表示 i i i 在 q q q 中的位置,则 p i = p o s i p_i = pos_i p i = p o s i ,可以将置换理解为走路这种东西然后就很好理解了。
所以其实就是求出 p o s pos p o s 之后两者的 l c p lcp l c p 。
那么这个题就很好解决了,可以直接对于每一个排列都求出 p o s pos p o s ,然后插入到 trie 树里,计算答案就是枚举每一个排列然后让它在字典树上走即可。
(我竟然一开始用了 bitset 维护这个过程,差点就过了)
代码:
点击查看代码
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 #include<bits/stdc++.h> using namespace std; const int N = 5e4+5; int n,m,a[N][11],tot = 1,pos[N][11],ch[100 * N][11]; void insert(int x){ int now = 1; for(int i=1; i<=m; i++){ int to = pos[x][i]; if(!ch[now][to]) ch[now][to] = ++tot; now = ch[now][to]; } } int find(int x){ int now = 1; int ans = 0; for(int i=1; i<=m; i++){ if(ch[now][a[x][i]]){ ++ans,now = ch[now][a[x][i]]; } else break; } return ans; } int main(){ // freopen("in.txt","r",stdin); // freopen("out.txt","w",stdout); int T;scanf("%d",&T); while(T--){ scanf("%d%d",&n,&m); for(int i=1; i<=n; i++){ for(int j=1; j<=m; j++){ scanf("%d",&a[i][j]); pos[i][a[i][j]] = j; } insert(i); } for(int i=1; i<=n; i++) printf("%d ",find(i)); printf("\n"); for(int i=1; i<=tot; i++){ for(int j=1; j<=10; j++){ ch[i][j] = 0; } } tot = 1; } return 0; }
E.Divisors and Table
题目描述:
给定一张 n × n n \times n n × n 的表格和一个正整数 m = m 1 × m 2 m = m_1 \times m_2 m = m 1 × m 2 ,表格第 i i i 行第 j j j 列的数 a i , j = i × j a_{i, j} = i \times j a i , j = i × j 。
现在需要你求出 m m m 的每个因子 d d d 是否在表格中出现,若出现,则求出其出现在表格中的最小行号。
1 ≤ n , m 1 , m 2 ≤ 1 0 9 1 \le n, m_1, m_2 \le 10^9 1 ≤ n , m 1 , m 2 ≤ 1 0 9
题目分析:
(一种乱搞做法)
1 0 18 10^{18} 1 0 1 8 以内的数,因子个数最多有大约 1 0 5 10^5 1 0 5 个,所以显然可以将 m m m 的的所有因子都拿出来然后挨个判断。
要求所有的因子其实就是要对 m m m 质因数分解,可以直接用科技对 m m m 分解,也可以分别为 m 1 , m 2 m_1,m_2 m 1 , m 2 分解后合起来,这样我们就得到了 m m m 的所有因子。
我们要做的其实就是分解 d = a × b d = a \times b d = a × b ,且 a , b ≤ n a,b \le n a , b ≤ n ,要求 a a a 尽可能小。
因为 d d d 为 m m m 的因子,所以 a , b a,b a , b 也必然是 m m m 的因子,所以可以将因子排序之后二分。
具体来说就是二分最小的 a a a 使得 ⌈ d a ⌉ ≤ n \lceil \frac{d}{a} \rceil \le n ⌈ a d ⌉ ≤ n ,这样只需要向后枚举一些 a a a 使得 a ∣ d a \mid d a ∣ d 此时 a a a 就是最优的 a a a 了。
代码:
点击查看代码
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 62 63 64 65 66 67 68 69 70 71 72 73 #include <iostream> #include <cstdio> #include <algorithm> #include <vector> using namespace std; #define int long long int t, n, m1, m2; vector<int> a, b, c; signed main() { scanf("%lld", &t); while (t -- ) { int cnt = 0, ans = 0; a.clear(); b.clear(); c.clear(); scanf("%lld%lld%lld", &n, &m1, &m2); for (int i = 1; i <= m1 / i; i ++ ) { if(m1 % i == 0) { a.push_back(i); if(m1 / i != i) a.push_back(m1 / i); } } for (int i = 1; i <= m2 / i; i ++ ) { if(m2 % i == 0) { b.push_back(i); if(m2 / i != i) b.push_back(m2 / i); } } sort(a.begin(), a.end()); sort(b.begin(), b.end()); for (int i = 0; i < a.size(); i ++ ) for (int j = 0; j < b.size(); j ++ ) c.push_back(a[i] * b[j]); sort(c.begin(), c.end()); c.erase(unique(c.begin(), c.end()), c.end()); for (int i = 0; i < c.size(); i ++ ) { int l = 0, r = i; while (l <= r) { int mid = l + r >> 1; if ((c[i] + c[mid] - 1) / c[mid] <= n) r = mid - 1; else l = mid + 1; } for (int j = l; j < c.size(); j ++ ) { if(c[j] > n) break; if(c[i] % c[j] == 0) { ans ^= c[j]; cnt ++; break; } } } printf("%lld %lld\n", cnt, ans); } }
F1.Graph Coloring
题目描述:
简单版和困难版之间的唯一区别是 n n n 的数据范围不同。
给出一个 n n n 个顶点的无向完全图。完全图是指图上任意两个顶点皆有一条边相连。你需要给图上的每条边染上红色或蓝色。
一个顶点的集合 S S S 被称作是红色连接 的,如果对于 S S S 中每对顶点 ( v 1 , v 2 ) (v_1,v_2) ( v 1 , v 2 ) ,都存在只通过红边和 S S S 中顶点的路径。相仿地,一个顶点的集合 S S S 被称作是蓝色连接 的,如果对于 S S S 中每对顶点 ( v 1 , v 2 ) (v_1,v_2) ( v 1 , v 2 ) ,都存在只通过蓝边和 S S S 中顶点的路径。
你需要以如下方式对图进行染色:
至少有一条红边。
至少有一条蓝边。
对于每个大小不小于 2 2 2 的顶点集 S S S (也即 ∣ S ∣ ⩾ 2 |S|\geqslant 2 ∣ S ∣ ⩾ 2 ),S S S 或者是红色连接的,或者是蓝色连接的,但不能同时是红色和蓝色连接的。
计算染色方法数对 998244353 998244353 9 9 8 2 4 4 3 5 3 取模后的结果。
1 ≤ n ≤ 5 × 1 0 3 1\le n \le 5\times 10^3 1 ≤ n ≤ 5 × 1 0 3
题目分析:
(不写 F2 了,是一个多项式科技)
题目条件就是要求对于任意一个导出子图,要么通过红色边可以联通,要么通过蓝色边可以联通,但是不能通过两种边都可以联通。
注意到一点就是:这是一个完全图,所以蓝色边和红色边互为补图,也就是如果蓝色边不连通则红色边必然联通,反之亦然。
所以我们可以直接 d p n dp_n d p n 表示节点个数为 n n n 的图,不能通过蓝色边联通的合法的方案数。
转移就是考虑枚举点 n n n 所在的蓝色连通块大小 x x x :
d p n = ∑ x = 1 n − 1 ( n − 1 x − 1 ) d p x × ( 2 − [ x = n − 1 ] ) d p n − x dp_n = \sum_{x=1}^{n-1} \binom{n-1}{x-1} dp_x \times (2 - [x = n-1])dp_{n-x}
d p n = x = 1 ∑ n − 1 ( x − 1 n − 1 ) d p x × ( 2 − [ x = n − 1 ] ) d p n − x
对于 n n n 所在的连通块这 x x x 个点必然满足蓝色联通也就是红色不连通,方案数等同于 d p x dp_x d p x 。
对于其余的 n − x n-x n − x 个点,它们之间蓝色联通或者红色联通都可以,所以就是系数有 2 2 2 的贡献,但当 n − x = 1 n-x = 1 n − x = 1 时,显然只有一种方案。
因为题目要求必须同时出现红边和蓝边,所以将答案减 2 2 2 即可。
代码:
点击查看代码
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 #include<bits/stdc++.h> #define int long long using namespace std; const int N = 5e3+5; const int MOD = 998244353; int fac[N],inv[N],f[N]; int binom(int n,int m){ if(n < m || n < 0 || m < 0) return 0; return fac[n] * inv[m] % MOD * inv[n-m] % MOD; } int power(int a,int b){ int res = 1; while(b){ if(b & 1) res = res * a % MOD; a = a * a % MOD; b >>= 1; } return res; } signed main(){ int n;scanf("%lld",&n); fac[0] = 1; for(int i=1; i<=n; i++) fac[i] = fac[i-1] * i % MOD; inv[n] = power(fac[n],MOD-2); for(int i=n-1; i>=0; i--) inv[i] = inv[i+1] * (i+1) % MOD; f[1] = 1; for(int i=2; i<=n; i++){ for(int j=1; j<=i-1; j++){ f[i] = (f[i] + f[j] * f[i-j] %MOD* binom(i-1,j-1) * (2 - (j == i-1))%MOD)%MOD; } } printf("%lld\n",(2 * f[n] %MOD - 2 + MOD)%MOD); return 0; }