【题解】Educational Codeforces Round 141(CF1783)
评价:educational
A.Make it Beautiful
题目描述:
如果一个数组中存在一个数恰好等于该数前面所有数之和,那么这个数组就是丑的。如果一个数组不是丑的,就是美的。
比如说:
数组 [ 6 , 3 , 9 , 6 ] [6, 3, 9, 6] [ 6 , 3 , 9 , 6 ] 是丑的,因为 9 = 6 + 3 9 = 6 + 3 9 = 6 + 3 ;
数组 [ 5 , 5 , 7 ] [5, 5, 7] [ 5 , 5 , 7 ] 是丑的,因为第二个 5 = 5 5 = 5 5 = 5 。
数组 [ 8 , 4 , 10 , 14 ] [8, 4, 10, 14] [ 8 , 4 , 1 0 , 1 4 ] 是美的,因为 8 ≠ 0 8 \ne 0 8 = 0 , 4 ≠ 8 4 \ne 8 4 = 8 , 10 ≠ 8 + 4 10 \ne 8 + 4 1 0 = 8 + 4 , 14 ≠ 8 + 4 + 10 14 \ne 8 + 4 + 10 1 4 = 8 + 4 + 1 0 ,没有任何一个数等于它前面的数之和。
给定数组 a a a 满足 1 ≤ a 1 ≤ a 2 ≤ ⋯ ≤ a n ≤ 100 1 \le a_1 \le a_2 \le \dots \le a_n \le 100 1 ≤ a 1 ≤ a 2 ≤ ⋯ ≤ a n ≤ 1 0 0 。 你可以任意调整元素的顺序,也可以不调整,使它变成一个美的数组。
题目分析:
我们可以考虑从大到小排序,这样除了最大值可能出问题,其它的都没问题。
而最大值就可以只保留一个在序列开头,其余的放到结尾即可。
代码:
点击查看代码
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 #include<bits/stdc++.h> using namespace std; const int N = 100; 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]); vector<int> v; for(int i=n; i>=1; i--){ if(a[i] == a[n] && i != n) continue; v.push_back(a[i]); } for(int i=n; i>=1; i--){ if(a[i] == a[n] && i != n) v.push_back(a[i]); } bool flag = true; int sum = 0; for(int i=0; i<(int)v.size(); i++){ if(v[i] == sum) flag = false; sum += v[i]; } if(flag){ printf("YES\n"); for(int i=0; i<(int)v.size(); i++) printf("%d ",v[i]); printf("\n"); } else printf("NO\n"); } return 0; }
B.Matrix of Differences
题目描述:
对于一个 n × n n\times n n × n 的矩阵,对于每一对相邻(有公共边)的值 a , b a,b a , b ,写下 ∣ a − b ∣ |a-b| ∣ a − b ∣ (即 a a a 与 b b b 差的绝对值)。定义这个矩阵的美丽度为写下的不同的值的个数。以如下的矩阵为例:
( 1 3 4 2 ) \left(\begin{matrix}1&3\\4&2\end{matrix}\right)
( 1 4 3 2 )
则所有相邻值的绝对值分别是 ∣ 1 − 3 ∣ = 2 , ∣ 1 − 4 ∣ = 3 , ∣ 3 − 2 ∣ = 1 , ∣ 4 − 2 ∣ = 2 |1-3|=2,|1-4|=3,|3-2|=1,|4-2|=2 ∣ 1 − 3 ∣ = 2 , ∣ 1 − 4 ∣ = 3 , ∣ 3 − 2 ∣ = 1 , ∣ 4 − 2 ∣ = 2 。共有 1 , 2 , 3 1,2,3 1 , 2 , 3 三种不同的值,则这个矩阵的美丽度为 3 3 3 。
给你 t t t 次询问,每次询问给定一个正整数 n n n 。输出任意一个 n × n n\times n n × n 的矩阵,满足 1 ∼ n 2 1\sim n^2 1 ∼ n 2 在矩阵中各出现一遍,并且该矩阵的美丽度最大。
1 ≤ t ≤ 49 , 2 ≤ n ≤ 50 1\le t\le49,2\le n\le50 1 ≤ t ≤ 4 9 , 2 ≤ n ≤ 5 0 。
题目分析:
手摸了半天才搞出来的做法。
考虑 n − 1 n-1 n − 1 会怎么得到,只有可能是 1 , n 1,n 1 , n ,那 n − 2 n-2 n − 2 呢?
可以是 1 , n − 1 1,n-1 1 , n − 1 或者 2 , n 2,n 2 , n ,为了构造的漂亮程度我们就不妨将 n , n − 1 n,n-1 n , n − 1 放到 1 1 1 的旁边,然后 2 2 2 继续放到下面,也就是下面这种方式:
1 n − 1 4 n − 5 ⋯ n 3 n − 4 ⋯ 2 n − 3 ⋯ n − 2 ⋯ ⋯ \begin{matrix}
1 &n-1 &4 &n-5 &\cdots\\
n &3 &n-4 &\cdots\\
2 &n-3 &\cdots\\
n-2 &\cdots\\
\cdots
\end{matrix}
1 n 2 n − 2 ⋯ n − 1 3 n − 3 ⋯ 4 n − 4 ⋯ n − 5 ⋯ ⋯
然后就可以过了。
代码:
点击查看代码
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 #include<bits/stdc++.h> using namespace std; const int N = 100; int a[N][N]; int main(){ // freopen("in.txt","r",stdin); // freopen("out.txt","w",stdout); int T;scanf("%d",&T); while(T--){ int n;scanf("%d",&n); int l = 1,r = n*n,tot = 0; for(int i=1; i<=n; i++){ ++tot; int nx = i,ny = 1; while(nx >= 1 && ny <= n){ if(tot & 1) a[nx][ny] = l,l++; else a[nx][ny] = r,--r; nx --,ny ++; } } for(int i=2; i<=n; i++){ ++tot; int nx = n,ny = i; while(nx >= 1 && ny <= n){ if(tot & 1) a[nx][ny] = l,l++; else a[nx][ny] = r,--r; nx --,ny ++; } } for(int i=1; i<=n; i++){ for(int j=1; j<=n; j++){ printf("%d ",a[i][j]); } printf("\n"); } } return 0; }
C.Yet Another Tournament
题目描述:
有 n n n 个选手,编号为 1 1 1 至 n n n ,每两个选手对战时,编号大的将会胜利。
你可以准备 m m m 单位时间,每准备 a i a_i a i 时间就可以赢 i i i 号选手。
按胜利的总次数排名,求你最高多少名。
题目分析:
一个想法就是我们直接将 a a a 最小到大排序,这样就可以赢尽可能多的场,看上去就是很好的排名。
但是我们的排名还与我们赢了哪些人有关,所以就有点不可做的样子。
注意到,当我们赢了 x x x 场就相当于要选择 n − x n-x n − x 个人多赢一场,然后寻找赢场数大于 x x x 的人的个数,而只有赢场数等于 x x x 的人会受到我们选择加一的影响,所以其实此时只需要判断能不能通过调整使得我们可以赢过胜场为 x x x 的人即可。
代码:
点击查看代码
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 #include<bits/stdc++.h> #define int long long using namespace std; const int N = 5e5+5; int a[N],b[N]; signed main(){ // freopen("in.txt","r",stdin); // freopen("out.txt","w",stdout); int T;scanf("%lld",&T); while(T--){ int n,m;scanf("%lld%lld",&n,&m); for(int i=1; i<=n; i++) scanf("%lld",&a[i]),b[i] = a[i]; sort(b+1,b+n+1); int pos = 0; for(int i=1; i<=n; i++){ if(m - b[i] >= 0){ pos = i; m -= b[i]; } } if(!pos){ printf("%lld\n",n+1); continue; } if(pos != n && a[pos+1] <= m + b[pos]) ++pos; printf("%lld\n",n-pos + 1); } return 0; }
D.Different Arrays
题目描述:
给你一个有 n n n 个元素的序列,你需要进行 n − 2 n-2 n − 2 次操作。
对于第 i i i 次操作,你可以选择让 a i − a i + 1 a_i-a_{i+1} a i − a i + 1 且 a i + 2 + a i + 1 a_{i+2}+a_{i+1} a i + 2 + a i + 1 或者可以选择让 a i + a i + 1 a_i+a_{i+1} a i + a i + 1 且 a i + 2 − a i + 1 a_{i+2}-a_{i+1} a i + 2 − a i + 1
问最后能产生多少个不同的序列。
题目分析:
一个想法就是判断什么样的序列是能被表示的,但是想了一会发现根本没有任何头绪,所以考虑换个想法,也就是直接使用 d p dp d p 去决策每一次的操作。
为了方便理解,我们将第 i i i 次操作,成为操作第 i + 1 i+1 i + 1 个数。
但是这样看上去有很多重复的情况就很难办,注意一点就是要使得产生相同的序列则必然满足存在 a i = 0 a_i = 0 a i = 0 的情况,然后操作 a i a_i a i ,否则一定不会产生相同的情况,所以我们完全不用考虑什么去重之类的问题,只需要判断 a i = 0 a_i = 0 a i = 0 即可。
所以可以考虑设 d p i , j dp_{i,j} d p i , j 表示操作完了前 i i i 个数,a i + 1 = j a_{i+1} = j a i + 1 = j 的方案数,记第二维的原因是我们此时需要决策第 i + 1 i+1 i + 1 次操作就必须知道对应的 a a a 是什么。
转移就是显然的,也就是直接枚举 a i + 1 a_{i+1} a i + 1 是怎么操作的,以及特判 a i + 1 = 0 a_{i+1} = 0 a i + 1 = 0 。
注意到第二维可以为负,所以加一个偏移量。
代码:
点击查看代码
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 #include<bits/stdc++.h> using namespace std; const int MOD = 998244353; const int MAX = 90000; int dp[305][90005 * 2]; int a[305]; void add(int &a,int b){ a = (a + b)%MOD; } int main(){ int n;scanf("%d",&n); for(int i=1; i<=n; i++) scanf("%d",&a[i]); dp[1][MAX+a[2]] = 1; for(int i=1; i<n; i++){ for(int k=-MAX; k<=MAX; k++){ if(!dp[i][k+MAX]) continue; if(k == 0){ dp[i+1][a[i+2]+MAX] = (dp[i+1][a[i+2]+MAX] + dp[i][k+MAX])%MOD; } else{ dp[i+1][a[i+2]+k+MAX] = (dp[i+1][a[i+2]+k+MAX] + dp[i][k+MAX])%MOD; dp[i+1][a[i+2]-k+MAX] = (dp[i+1][a[i+2]-k+MAX] + dp[i][k+MAX])%MOD; } } } int ans = 0; for(int j=-MAX; j<=MAX; j++) add(ans,dp[n-1][j+MAX]); printf("%d\n",ans); return 0; }
E.Game of the Year
题目描述:
Monocarp 和 Polycarp 正在玩电脑游戏。游戏特点:n n n 个编号从 1 1 1 到 n n n 的BOSS。
他俩将用以下方式与BOSS战斗
Monocarp 进行 k k k 次尝试撒掉boss;
Polycarp 进行 k k k 次尝试撒掉boss;
Monocarp 进行 k k k 次尝试撒掉boss;
Polycarp 进行 k k k 次尝试撒掉boss;
…
Monocarp 在第 a i a_i a i 次尝试中撒掉了第 i i i 只BOSS。Polycarp 在第 b i b_i b i 次尝试中撒掉了第 i i i 只BOSS。其中一个人撒掉第 i i i 只BOSS后,他们就会尝试撒第 ( i + 1 ) (i+1) ( i + 1 ) 只BOSS。并且他们的尝试计数器都会清空。撒掉第 n n n 只BOSS后,游戏结束。
找到从1 1 1 到 n n n 所有的 k k k 值, 使得 Monocarp 可以杀死所有的BOSS。
1 ≤ n ≤ 2 × 1 0 5 1 \le n \le 2\times 10^5 1 ≤ n ≤ 2 × 1 0 5
题目分析:
题目说的实在是太抽象了,转化一下题意就是要找到满足以下条件的 k k k :
∀ i ∈ [ 1 , n ] ⌈ a i k ⌉ ≤ ⌈ b i k ⌉ \begin{aligned}
&\forall i\in[1,n] & \lceil \frac{a_i}{k} \rceil \le \lceil \frac{b_i}{k} \rceil
\end{aligned}
∀ i ∈ [ 1 , n ] ⌈ k a i ⌉ ≤ ⌈ k b i ⌉
首先就是可以直接整除分块就能找到所有满足条件的 k k k ,复杂度 O ( n n ) O(n\sqrt{n}) O ( n n ) 但是常数有点逆天据说不能过,考虑优化。
一个经典的想法就是既然不能枚举约数,那么我们就枚举倍数,即枚举 k k k 然后枚举 k k k 的倍数。
可以发现 k k k 的倍数将序列分成了 O ( n k ) O(\frac{n}{k}) O ( k n ) 段,而要使得上述条件满足就是 a i a_i a i 所在的块不在 b i b_i b i 所在的块后面。
如果原来就满足 a i ≤ b i a_i \le b_i a i ≤ b i 则无论如何都满足条件,就不管了。
如果 a i > b i a_i > b_i a i > b i 条件其实就是 a i a_i a i 所在的块与 b i b_i b i 所在的块相同,这个不是很好判,那么什么时候是不在一块呢?
既然不在一块也就是说 [ b i , a i ] [b_i,a_i] [ b i , a i ] 跨过了一个分界点,如果我们以 k k k 的倍数作为每一段的右端点,也就是 [ b i , a i ) [b_i,a_i) [ b i , a i ) 包含 k k k 的倍数。
可以直接预处理出每一个位置是否可以作为右端点,然后对于每一个 k k k 的倍数判断一下即可。
复杂度 O ( n log n ) O(n \log n) O ( n log n )
代码:
点击查看代码
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 #include<bits/stdc++.h> using namespace std; const int N = 5e5+5; int a[N],b[N],sum[N]; vector<int> v; int main(){ // freopen("in.txt","r",stdin); // freopen("out.txt","w",stdout); int T;scanf("%d",&T); while(T--){ int n;scanf("%d",&n); for(int i=1; i<=n; i++) scanf("%d",&a[i]); for(int i=1; i<=n; i++) scanf("%d",&b[i]); for(int i=1; i<=n; i++){ if(a[i] > b[i]) sum[b[i]]++,sum[a[i]]--; } for(int i=1; i<=n; i++) sum[i] += sum[i-1]; for(int i=1; i<=n; i++){ bool flag = true; for(int j=i; j<=n; j+=i){ if(sum[j]) flag = false; } if(flag) v.push_back(i); } printf("%d\n",v.size()); for(int i=0; i<v.size(); i++) printf("%d ",v[i]); printf("\n"); for(int i=0; i<=n; i++) sum[i] = 0; v.clear(); } return 0; }
F.Double Sort II
题目描述:
有两个 1.. n 1..n 1 . . n 的排列 a , b a,b a , b 。
你可以进行若干次操作,每次操作流程如下:
选择一个整数 i ∈ [ 1 , n ] i \in [1,n] i ∈ [ 1 , n ] 。
找出两个整数 x , y x,y x , y ,使得 a x = b y = i a_x=b_y=i a x = b y = i 。
交换 a x a_x a x 和 a i a_i a i ,以及 b y b_y b y 和 b i b_i b i 。
问把 a a a 和 b b b 从小到大排序的最小操作次数
题目分析:
考虑将排列看作一个置换,然后建图,也就是连边 i → a i i \to a_i i → a i 与 i → b i i \to b_i i → b i ,注意这是两张图。
我们的一次操作相当于将某个点缩成一个自环,其他点不受影响,所以对于每一个置换环设其长度为 l e n len l e n 只需要操作 l e n − 1 len-1 l e n − 1 次就可以将所有点缩成自环,即我们可以对于每一个环钦定一个点使得这个点不被操作,要最大化钦定点的数量。
而两张图其实也是差不多的,如果钦定 i i i 不被操作,也就是说 i i i 在 a , b a,b a , b 中的环上均只能选择 i i i 这一个点不被操作,这个其实就是一个匹配的感觉。
所以可以对于每一个点 i i i ,找到其在 a , b a,b a , b 上的环,将这两个环连边,最后跑一个最大匹配就是最多的不用被操作的点数。
代码:
点击查看代码
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 74 75 #include<bits/stdc++.h> using namespace std; const int N = 1e5+5; struct edge{ int nxt,to,id; edge(){} edge(int _nxt,int _to,int _id){ nxt = _nxt,to = _to,id = _id; } }e[2 * N]; int cnt,col1[N],col2[N],head[N],flag[N],match[N],a[N],b[N]; bool vis[N],tag[N]; void add_edge(int from,int to,int id){ e[++cnt] = edge(head[from],to,id); head[from] = cnt; } bool dfs(int now){ if(vis[now]) return false; vis[now] = true; for(int i=head[now] ;i ; i=e[i].nxt){ int to = e[i].to; if(!match[to] || dfs(match[to])){ match[now] = to,match[to] = now; flag[now] = flag[to] = e[i].id; return true; } } return false; } void dfs1(int now,int col){ if(col1[now]) return; col1[now] = col; if(!col1[a[now]]) dfs1(a[now],col); } void dfs2(int now,int col){ if(col2[now]) return; col2[now] = col; if(!col2[b[now]]) dfs2(b[now],col); } int main(){ // freopen("in.txt","r",stdin); // freopen("out.txt","w",stdout); int n;scanf("%d",&n); for(int i=1; i<=n; i++) scanf("%d",&a[i]); for(int i=1; i<=n; i++) scanf("%d",&b[i]); int tot = 0; for(int i=1; i<=n; i++){ if(col1[i]) continue; ++tot;dfs1(i,tot); } int tmp = tot; memset(vis,false,sizeof(vis)); for(int i=1; i<=n; i++){ if(col2[i]) continue; ++tot;dfs2(i,tot); } for(int i=1; i<=n; i++){ add_edge(col1[i],col2[i],i); add_edge(col2[i],col1[i],i); // printf("%d %d\n",col1[i],col2[i]); } int ans = 0; for(int i=1; i<=tmp; i++){ memset(vis,false,sizeof(vis)); ans += dfs(i); } printf("%d\n",n - ans); for(int i=1; i<=tmp; i++){ tag[flag[i]] = true; } for(int i=1; i<=n; i++){ if(!tag[i]) printf("%d ",i); } return 0; }
G.Weighed Tree Radius
题目描述:
给你一个n n n 个点的树和n − 1 n-1 n − 1 条边。第i i i 个点的初始权值为a i a_i a i 。
定义结点v v v 到结点u u u 的距离d v ( u ) d_v(u) d v ( u ) 等于v v v 和u u u 之间的边的数量。注意:d v ( u ) = d u ( v ) , d v ( v ) = 0 d_v(u)=d_u(v),d_v(v)=0 d v ( u ) = d u ( v ) , d v ( v ) = 0
定义结点v v v 到结点u u u 的权值距离w v ( u ) = d v ( u ) + a u w_v(u)=d_v(u)+a_u w v ( u ) = d v ( u ) + a u 。注意:w v ( v ) = a v , w v ( u ) ≠ w u ( v ) w_v(v)=a_v,w_v(u) \neq w_u(v) w v ( v ) = a v , w v ( u ) = w u ( v ) 如果a u ≠ a v a_u \neq a_v a u = a v
与通常的距离类似,让我们定义结点v v v 的偏心距e ( v ) e(v) e ( v ) 是从v v v 到其他结点的最大权值距离(包括v v v 本身) ,即e ( v ) = max 1 ≤ u ≤ n w v ( u ) e(v)=\max\limits_{1\leq u \leq n} w_v(u) e ( v ) = 1 ≤ u ≤ n max w v ( u ) 。
最后,我们定义树的半径r r r 是所有偏心距的最小值,即r = min 1 ≤ v ≤ n e ( v ) r=\min\limits_{1\leq v\leq n} e(v) r = 1 ≤ v ≤ n min e ( v )
你需要对m m m 次询问进行回答,对于第j j j 次询问,给出两个数v j v_j v j 和x j x_j x j ,表示将a v j a_{v_j} a v j 的值修改为x j x_j x j 。
在每次询问后,输出当前该树的半径r r r 。
2 ≤ n ≤ 2 × 1 0 5 , 1 ≤ m ≤ 1 0 5 2 \le n \le 2 \times 10^5,1\le m \le 10^5 2 ≤ n ≤ 2 × 1 0 5 , 1 ≤ m ≤ 1 0 5
题目分析:
题目已经提示了这东西叫做半径,那么是不是直接求直径然后除以 2 2 2 就可以呢?
我们定义 w ′ ( u , v ) = a u + a v + d i s ( u , v ) w'(u,v) = a_u + a_v + dis(u,v) w ′ ( u , v ) = a u + a v + d i s ( u , v ) ,那么满足 w ′ ( u , v ) w'(u,v) w ′ ( u , v ) 最大的两个点 u , v u,v u , v 之间的路径的长度我们称为直径。
这里我们将 a u a_u a u 理解为挂在 u u u 上长度为 a u a_u a u 的链,a v a_v a v 理解为挂在 v v v 上长度为 a v a_v a v 的链。
设直径的中点为 m i d mid m i d ,若 m i d mid m i d 在直径的某一个节点上,则显然 r = ⌈ w ′ ( u , v ) 2 ⌉ r = \lceil \frac{w'(u,v)}{2} \rceil r = ⌈ 2 w ′ ( u , v ) ⌉ ,可是如果 m i d mid m i d 不在直径的某一个节点上呢。
若 m i d mid m i d 在 a u a_u a u 对应的链上,则必然满足 e u = a u e_u = a_u e u = a u ,则对于其它的任意一个点 x x x 都必然满足 e x ≥ d i s ( u , x ) + a u > a u = e u e_x \ge dis(u,x) + a_u > a_u = e_u e x ≥ d i s ( u , x ) + a u > a u = e u ,即 r = a u r = a_u r = a u ,但是这样的话就必然满足直径为 w ′ ( u , u ) w'(u,u) w ′ ( u , u ) 就不可能不存在了。
下面我们的问题就转化为了维护直径。
考虑假设我们原来的直径为 ( u , v ) (u,v) ( u , v ) 现在将 a x a_x a x 增大了一些,那么我们新直径的端点必然是 u , v , x u,v,x u , v , x 其中的两个,可以直接分类讨论得到答案。
而如果我们将 a x a_x a x 减小了一些,我们就无法判断直径端点的变化,所以可以考虑使用线段树分治维护修改,这样每次将值从 0 0 0 开始变化,这样每次都是加的操作了。