总结一下,这场做的不是太好,状态不好。
A题
知识点:分类讨论
题意:给了 n n n个不同的整数点 x 1 , x 2 , ... , x n x_1,x_2,\dots,x_n x1,x2,...,xn和一个整数 s s s, s s s是起始点,相当于 x x x轴上有 n n n个不同的整数点,你起始在 s s s,需要经过每一个 x i x_i xi至少一次。现在有操作可以每次使当前位置+1或者-1。问经过每个点至少一次需要的最少操作次数。
解题:将 n n n个点升序排序,然后分类讨论不同情况下的可能答案 a n s ans ans: s < = a [ 1 ] ∣ ∣ s > = a [ n ] s<=a[1]||s>=a[n] s<=a[1]∣∣s>=a[n],那么都只需要从 s s s移动到另一个端点即可,即 a n s = m i n ( s − a [ 1 ] , a [ n ] − s ) ans=min(s-a[1],a[n]-s) ans=min(s−a[1],a[n]−s);如果 s > a [ 1 ] & & s < a [ n ] s>a[1]\&\&s<a[n] s>a[1]&&s<a[n],那么取 a n s = m i n ( a [ n ] − s , s − a [ 1 ] ) + a [ n ] − a [ 1 ] ans=min(a[n]-s,s-a[1])+a[n]-a[1] ans=min(a[n]−s,s−a[1])+a[n]−a[1]。
c++
void solve(){
int n,s;cin >> n >> s;
vector<int> a(n+1);
for(int i=1;i<=n;i++){
cin >> a[i];
}
sort(a.begin()+1,a.end());
int ans=inf;
if(s<=a[1]){
ans=a[n]-s;
}else if(s>=a[n]){
ans=s-a[1];
}else{
ans=a[n]-a[1]+min(abs(a[n]-s),abs(s-a[1]));
}
cout << ans << endl;
}
B题
知识点:贪心
题意:就是给定一个字符串 s s s, s s s是由三个连续字串 a , b , c a,b,c a,b,c拼接而成,也就是 s = a + b + c s=a+b+c s=a+b+c。现在 b b b是 a + c a+c a+c的一个字串,问是否存在三个非空字串 a , b , c a,b,c a,b,c满足前面两个条件。
解题:那么可以这样想,贪心的 b b b越短,那么 b b b越可能是 a + c a+c a+c的字串,那么就从 1 1 1到 n − 1 n-1 n−1枚举字符串 a a a,因为 b b b非空,所以保留一个,那么枚举到当前字符s[i]
时,就看前面是否出现过,出现过就返回yes
,由于 b b b也可能是 c c c的字串,那么将 s s s翻转,再check
一次,取两次的结果并起来。
c++
int check(string s,int n){
set<char> se;
for(int i=1;i<n;i++){
if(!se.count(s[i])){
se.insert(s[i]);
}else{
return 1;
}
}
return 0;
}
void solve(){
int n;cin >> n;
string s;cin >> s;
string s1=s;
reverse(s1.begin(),s1.end());
s=" "+s;
s1=" "+s1;
if(check(s,n)||check(s1,n)){
cout << "Yes" << endl;
}else{
cout << "No" << endl;
}
}
C题
知识点:贪心
题意:
给你一个具有 n n n 行 m m m 列的整数矩阵。第 i i i 行和第 j j j 列交叉处的单元格中的数字为 a i j a_{ij} aij 。
你可以恰好执行一次以下操作:
- 选择两个数字 1 ≤ r ≤ n 1 \leq r \leq n 1≤r≤n 且 1 ≤ c ≤ m 1 \leq c \leq m 1≤c≤m 。
- 对于矩阵中所有满足 i = r i = r i=r 或 j = c j = c j=c 的单元格 ( i , j ) (i, j) (i,j) ,将 a i j a_{ij} aij 的值减1 。
你需要在恰好执行一次这样的操作后,找出矩阵 a a a 中可能的最小最大值(即矩阵中最大元素的最小可能值 )。
输入
每个测试包含多个测试用例。第一行包含一个整数 t t t( 1 ≤ t ≤ 10 4 1 \leq t \leq 10^4 1≤t≤104 )------ 测试用例的数量。接下来是测试用例的描述。
每个测试用例的第一行包含两个整数 n n n 和 m m m( 1 ≤ n ⋅ m ≤ 10 5 1 \leq n \cdot m \leq 10^5 1≤n⋅m≤105 )------ 矩阵的行数和列数。
每个测试用例接下来的 n n n 行描述矩阵 a a a 。第 i i i 行包含 m m m 个整数 a i 1 , a i 2 , ... , a i m a_{i1}, a_{i2}, \ldots, a_{im} ai1,ai2,...,aim( 1 ≤ a i j ≤ 100 1 \leq a_{ij} \leq 100 1≤aij≤100 )------ 矩阵第 i i i 行的元素。
保证所有测试用例的 n ⋅ m n \cdot m n⋅m 之和不超过 2 × 10 5 2 \times 10^5 2×105 。
输出
对于每个测试用例,输出在恰好执行一次操作后,矩阵 a a a 中可能的最小最大值。
解题:这道题就是贪心的选取一行一列使得包含最大值的个数最多就好,先统计每一行和每一列mx
的最大值个数,以及统计矩阵mx
的个数tot
,因为答案为mx
或者mx-1
,主要看选取的一行一列包含的mx
的个数 m a x ( c n t [ r ] + c n t [ c ] ) = = t o t max(cnt[r]+cnt[c])==tot max(cnt[r]+cnt[c])==tot,注意一个就是如果 a [ r ] [ c ] = = m x a[r][c]==mx a[r][c]==mx,那么 c n t [ r ] + c n t [ c ] cnt[r]+cnt[c] cnt[r]+cnt[c]要减1。那么可以考虑枚举所有的行和列取最大的和就可以了,因为 n ∗ m n*m n∗m的和不超过2e5
。所以枚举不会超时。优化可以将 r o w row row和 c o l col col排序,贪心的从大开始选择,但是要注意如果有多行最大值个数相同,那么选择不同列,mx
的和会不一样,注意细节就好。
c++
int check(const vector<vector<int>> &a, int n, int m) {
int mx = 0;
for(int i=1; i<=n; i++) {
for(int j=1; j<=m; j++) {
mx = max(mx, a[i][j]);
}
}
vector<int> row(n+1, 0);
vector<int> col(m+1, 0);
int total_max = 0;
for(int i=1; i<=n; i++) {
for(int j=1; j<=m; j++) {
if(a[i][j] == mx) {
row[i]++;
col[j]++;
total_max++;
}
}
}
int best = 0;
for(int r=1; r<=n; r++) {
for(int c=1; c<=m; c++) {
int count = row[r] + col[c];
if(a[r][c] == mx) count--;
best = max(best, count);
}
}
return (best == total_max) ? mx-1 : mx;
}
void solve() {
int n, m;
cin >> n >> m;
vector<vector<int>> a(n+1, vector<int>(m+1, 0));
for(int i=1; i<=n; i++) {
for(int j=1; j<=m; j++) {
cin >> a[i][j];
}
}
int ans = inf;
ans = min(ans, check(a, n, m));
cout << ans << endl;
}
D题
知识点:冒泡排序
题意:
给你两个整数数组 a 1 , a 2 , ... , a n a_1, a_2, \ldots, a_n a1,a2,...,an 和 b 1 , b 2 , ... , b n b_1, b_2, \ldots, b_n b1,b2,...,bn。保证从 1 到 2 ⋅ n 2 \cdot n 2⋅n 的每个整数恰好出现在其中一个数组里。
你需要执行若干次(可能为0次)操作,使得以下两个条件都得到满足:
- 对于每个 1 ≤ i < n 1 \leq i < n 1≤i<n,有 a i < a i + 1 a_i < a_{i + 1} ai<ai+1 且 b i < b i + 1 b_i < b_{i + 1} bi<bi+1。
- 对于每个 1 ≤ i ≤ n 1 \leq i \leq n 1≤i≤n,有 a i < b i a_i < b_i ai<bi。
在每次操作中,你恰好可以执行以下三个操作之一:
- 选择一个索引 1 ≤ i < n 1 \leq i < n 1≤i<n,交换 a i a_i ai 和 a i + 1 a_{i + 1} ai+1 的值。
- 选择一个索引 1 ≤ i < n 1 \leq i < n 1≤i<n,交换 b i b_i bi 和 b i + 1 b_{i + 1} bi+1 的值。
- 选择一个索引 1 ≤ i ≤ n 1 \leq i \leq n 1≤i≤n,交换 a i a_i ai 和 b i b_i bi 的值。
你无需最小化操作次数,但总操作次数必须不超过 1709。找出满足条件的任意一个操作序列。
输入
每个测试包含多个测试用例。第一行包含一个整数 t t t( 1 ≤ t ≤ 100 1 \leq t \leq 100 1≤t≤100)------ 测试用例的数量。接下来是测试用例的描述。
每个测试用例的第一行包含一个整数 n n n( 1 ≤ n ≤ 40 1 \leq n \leq 40 1≤n≤40)------ 数组 a a a 和 b b b 的长度。
每个测试用例的第二行包含 n n n 个整数 a 1 , a 2 , ... , a n a_1, a_2, \ldots, a_n a1,a2,...,an( 1 ≤ a i ≤ 2 ⋅ n 1 \leq a_i \leq 2 \cdot n 1≤ai≤2⋅n)。
每个测试用例的第三行包含 n n n 个整数 b 1 , b 2 , ... , b n b_1, b_2, \ldots, b_n b1,b2,...,bn( 1 ≤ b i ≤ 2 ⋅ n 1 \leq b_i \leq 2 \cdot n 1≤bi≤2⋅n)。
保证从 1 到 2 ⋅ n 2 \cdot n 2⋅n 的每个整数要么出现在数组 a a a 中,要么出现在数组 b b b 中。
输出
对于每个测试用例,输出操作序列。
对于每个测试用例,第一行输出操作次数 k k k。注意 0 ≤ k ≤ 1709 0 \leq k \leq 1709 0≤k≤1709。
接下来的 k k k 行,每行输出操作本身:
- 如果你想交换 a i a_i ai 和 a i + 1 a_{i + 1} ai+1 的值,输出两个整数 1 和 i i i。注意 1 ≤ i < n 1 \leq i < n 1≤i<n。
- 如果你想交换 b i b_i bi 和 b i + 1 b_{i + 1} bi+1 的值,输出两个整数 2 和 i i i。注意 1 ≤ i < n 1 \leq i < n 1≤i<n。
- 如果你想交换 a i a_i ai 和 b i b_i bi 的值,输出两个整数 3 和 i i i。注意 1 ≤ i ≤ n 1 \leq i \leq n 1≤i≤n。
可以证明,在给定约束下,解一定存在。
解题:首先分别对 a a a和 b b b进行冒泡排序,同时统计答案。排序之后保证了 a a a和 b b b都是递增的。然后从前往后枚举 i i i,只要遇到 a i > b i a_i>b_i ai>bi,那么就交换,统计答案,我们考虑交换了之后是否满足约束: a i > a i − 1 , b i > b i − 1 , b i − 1 > a i − 1 a_i>a_{i-1},b_i>b_{i-1},b_{i-1}>a_{i-1} ai>ai−1,bi>bi−1,bi−1>ai−1,那么交换后 b i > b i − 1 > a i − 1 , a i > b i > b i − 1 b_i>b_{i-1}>a_{i-1},a_i>b_i>b_{i-1} bi>bi−1>ai−1,ai>bi>bi−1依然成立,所以保证 i i i前面所有 a i < b i a_i<b_i ai<bi,那么遇到 a i + 1 > b i + 1 a_{i+1}>b_{i+1} ai+1>bi+1时,交换之后依然满足约束条件。
c++
void solve(){
int n;cin >> n;
vector<int> a(n+1),b(n+1);
for(int i=1;i<=n;i++){
cin >> a[i];
}
for(int i=1;i<=n;i++) cin >> b[i];
vector<pair<int,int>> ans;
for(int i=1;i<n;i++){
for(int j=1;j+1<=n-i+1;j++){
if(a[j]>a[j+1]){
swap(a[j],a[j+1]);
ans.push_back({1,j});
}
}
}
for(int i=1;i<n;i++){
for(int j=1;j+1<=n-i+1;j++){
if(b[j]>b[j+1]){
swap(b[j],b[j+1]);
ans.push_back({2,j});
}
}
}
for(int i=1;i<=n;i++){
if(a[i]>b[i]){
swap(a[i],b[i]);
ans.push_back({3,i});
}
}
cout << ans.size() << endl;
for(auto [i,j]:ans){
cout << i << " " << j << endl;
}
}
E题
知识点:分类讨论
题意:
对于两个整数 a a a 和 b b b,我们定义 f ( a , b ) f(a, b) f(a,b) 为 a a a 和 b b b 的十进制表示中,对应位置上数字相同的位数。例如, f ( 12 , 21 ) = 0 f(12, 21) = 0 f(12,21)=0, f ( 31 , 37 ) = 1 f(31, 37) = 1 f(31,37)=1, f ( 19891 , 18981 ) = 2 f(19891, 18981) = 2 f(19891,18981)=2, f ( 54321 , 24361 ) = 3 f(54321, 24361) = 3 f(54321,24361)=3。
给你两个十进制表示长度相同 的整数 l l l 和 r r r。考虑所有满足 l ≤ x ≤ r l \leq x \leq r l≤x≤r 的整数 x x x。你的任务是找出 f ( l , x ) + f ( x , r ) f(l, x) + f(x, r) f(l,x)+f(x,r) 的最小值。
输入
每个测试包含多个测试用例。第一行包含一个整数 t t t( 1 ≤ t ≤ 10 4 1 \leq t \leq 10^4 1≤t≤104)------ 测试用例的数量。接下来是测试用例的描述。
每个测试用例包含一行,有两个整数 l l l 和 r r r( 1 ≤ l ≤ r < 10 9 1 \leq l \leq r < 10^9 1≤l≤r<109)。
保证 l l l 和 r r r 的十进制表示长度相同,且没有前导零。
输出
对于每个测试用例,输出在所有满足 l ≤ x ≤ r l \leq x \leq r l≤x≤r 的整数 x x x 中, f ( l , x ) + f ( x , r ) f(l, x) + f(x, r) f(l,x)+f(x,r) 的最小值。
解题: l , r l,r l,r位数相同, f ( l , r ) f(l,r) f(l,r)表示 l , r l,r l,r相同位置处 l [ i ] = = r [ i ] l[i]==r[i] l[i]==r[i]的位置数,那么首先考虑 l l l和 r r r的最长公共前缀pre
,因为 x ∈ [ l , r ] x \in [l,r] x∈[l,r],那么 x x x的开始肯定是 p r e pre pre。之后考虑第一位不同的位置 p t r ptr ptr,如果 r [ p t r ] − l [ p t r ] > 1 r[ptr]-l[ptr]>1 r[ptr]−l[ptr]>1,那么 x x x的这一位可以选择 [ l [ p t r ] + 1 , r [ p t r ] − 1 ] [l[ptr]+1,r[ptr]-1] [l[ptr]+1,r[ptr]−1]中的一位,这样在剩下的 [ p t r + 1 , n ] [ptr+1,n] [ptr+1,n],可以随意的选择 n u m ≠ l [ i ] & & n u m ≠ r [ i ] num \not= l[i]\&\&num\not=r[i] num=l[i]&&num=r[i]中的8位数字,这样答案就为 l e n ( p r e ) × 2 len(pre)\times 2 len(pre)×2。
如果 r [ p t r ] − l [ p t r ] = = 1 r[ptr]-l[ptr]==1 r[ptr]−l[ptr]==1,那么第 p t r ptr ptr处要么选择 l [ p t r ] l[ptr] l[ptr],要么选择 r [ p t r ] r[ptr] r[ptr]。考虑例子:
l=1237854,r=1246431
那么x前3位选择123
第4位可以选择一个大于等于7的数,那么我们选择8和9中的一个,可以看到后面可以随便选择了,答案为6
x前3位选择124
第4位选择小于等于6的数,我们选择5,之后也可以随便选了
那么考虑这样的例子,当第4位为9时,你找不到大于9的那一位了,你只能选9,就对答案有贡献
比如l=1239965,r=1246731
那么x以123开头的话,第4位只能是9,第5位只能是9,然后下一位选个7
然后看这个例子l=1239991,r=1240003
那么x要么等于1239992,要么等于1240002
这样就有5位了,也就是从ptr+1开始l[i]=9&&r[i]=0,那么这里就必然有一个贡献,否则你都可以通过选择不产生贡献。
所以做法总结为:
找到最长公共前缀
找到第一位不同的地方:
(1)如果r[i]-l[i]>1 ans=2*len(pre)
(2)如果r[i]-l[i]==1 ans=2*len(pre)+1+下一位接着的连续9和0的位置数
c++
void solve() {
string l, r;
cin >> l >> r;
int n = l.size();
if(l==r){
cout << 2*n << endl;
return;
}
int ptr=0;//最长公共前缀
int ans=0;
while(ptr<n&&l[ptr]==r[ptr]) ptr++;
if(r[ptr]-l[ptr]>1){
ans=2*ptr;
}else{
//等于1
ans=2*ptr+1;
for(int i=ptr+1;i<n;i++){
if(l[i]=='9'&&r[i]=='0') ans++;
else break;
}
}
cout << ans << endl;
}
F题
知识点:区间和,区间最大值
题意:
给你一个整数数组 a 1 , a 2 , ... , a n a_1, a_2, \ldots, a_n a1,a2,...,an 以及两个整数 s s s 和 x x x。统计数组中满足以下条件的子段(连续子数组)数量:子段元素和等于 s s s,且子段中的最大值等于 x x x。
更形式化地说,统计满足 1 ≤ l ≤ r ≤ n 1 \leq l \leq r \leq n 1≤l≤r≤n 的数对 ( l , r ) (l, r) (l,r) 的数量,使得:
- a l + a l + 1 + ... + a r = s a_l + a_{l + 1} + \ldots + a_r = s al+al+1+...+ar=s
- max ( a l , a l + 1 , ... , a r ) = x \max(a_l, a_{l + 1}, \ldots, a_r) = x max(al,al+1,...,ar)=x
输入
每个测试包含多个测试用例。第一行包含一个整数 t t t( 1 ≤ t ≤ 10 4 1 \leq t \leq 10^4 1≤t≤104)------ 测试用例的数量。接下来是测试用例的描述。
每个测试用例的第一行包含三个整数 n , s , x n, s, x n,s,x( 1 ≤ n ≤ 2 × 10 5 1 \leq n \leq 2 \times 10^5 1≤n≤2×105, − 2 × 10 14 ≤ s ≤ 2 × 10 14 -2 \times 10^{14} \leq s \leq 2 \times 10^{14} −2×1014≤s≤2×1014, − 10 9 ≤ x ≤ 10 9 -10^9 \leq x \leq 10^9 −109≤x≤109)。
每个测试用例的第二行包含 n n n 个整数 a 1 , a 2 , ... , a n a_1, a_2, \ldots, a_n a1,a2,...,an( − 10 9 ≤ a i ≤ 10 9 -10^9 \leq a_i \leq 10^9 −109≤ai≤109)。
保证所有测试用例的 n n n 之和不超过 2 × 10 5 2 \times 10^5 2×105。
输出
对于每个测试用例,输出满足以下条件的数组子段数量:子段元素和等于 s s s 且子段中的最大值等于 x x x。
解题:拿到题第一思路,区间和等于 s s s的数目可以通过前缀和和map来维护,所以我们可以开一个map<int,set<int>>
来维护每一个前缀和sum
的那些位置pos
,之后找一个维护区间最大值的数据结构ST
表,对于每个位置 i i i,目标值为 p r e [ i ] − s pre[i]-s pre[i]−s,我们去枚举 m p [ p r e [ i ] − s ] mp[pre[i]-s] mp[pre[i]−s]的那些位置,每次用 O ( 1 ) O(1) O(1)的时间复杂度查询这个位置是否合法,这样最后最坏时间复杂度可能为 O ( n 2 ) O(n^2) O(n2),所以在test 7 TLE
了。
之后换了一种思路,我们不需要去枚举满足前缀和的每一个位置,只需要先预处理出每个点 i i i向左的最小L0
和最大L1
,这样每次我们只用在那些满足前缀和为目标值的那些点中二分满足的位置个数,之后做好细节处理就行了。但是这样也还是在test 14
超时了,时间复杂度为 O ( n × log ( n ) ) O(n\times \log(n)) O(n×log(n)),但是map和set的常数比较大,考虑到插入进去的位置肯定是有序的,因为使用vector替代set,用unordered_map替代map,变为unordered_map<int,vector<int>> mp
来替代,时间复杂度为 O ( n × log ( n ) ) O(n\times\log(n)) O(n×log(n)),结果还是在test 30
超时了。
最后就是标准做法,可以看到上面我们还是用的map来维护前缀和,只是我们想知道前缀和里面的那些位置合法,那么如果我们每次能够保证map中的所有位置对于当前位置 i i i都是合法的,那么就不用再来维护每个位置 i i i的合法区间了。所以STD
是用双指针+map<int,int>
来维护信息,枚举位置统计答案。具体做法为:
c++
void solve(){
int n,s,x;cin >> n >> s >> x;
vector<int> a(n+1),pre(n+1,0);
for(int i=1;i<=n;i++){
cin >> a[i];
pre[i]=pre[i-1]+a[i];
}
int ans=0,lef=1;
map<int,int> cnt;
for(int r=1;r<=n;r++){
if(a[r]>x){
cnt.clear();
lef=r+1;
}else if(a[r]==x){
//要将[lef-1,r]的所有前缀加进去
while(lef<=r){
cnt[pre[lef-1]]++;
lef++;
}
}
ans+=cnt[pre[r]-s];
}
cout << ans << endl;
}
这样时间复杂度当然小多了,差不多为 O ( n ) O(n) O(n)了,因为如果位置 i i i的值 a [ i ] > x a[i]>x a[i]>x,那么所有前缀 p r e [ 1 ] , p r e [ 2 ] , ... , p r e [ i − 1 ] pre[1],pre[2],\dots,pre[i-1] pre[1],pre[2],...,pre[i−1]就都没用了,之后每找到一个 x x x,就让 l e f lef lef向右移动这个合法区间的前缀和,最后在r
处统计答案。