9.25训练赛+Codeforces1054 (Div. 3)
9.25训练赛+Codeforces1054 (Div. 3)
这学期总共4个月现在居然已经过了1/4. 感觉剩的时间好少呀,水平长进不大,好像暑假就是这个水平。训练赛是1300~1400的难度,之前没刷过,和1100差的还挺多。不过大家写的那道还是不难的,我没想明白为啥不考虑顺序。晚上又有场cf,借着一起补了。
A - Klee’s SUPER DUPER LARGE Array!!!
题目转化一下就是求|前缀和-后缀和|最小值。
- 由于绝对值的缘故最小值肯定是集中在0的附近,本题很明显是一个二分
- 如果直接带上绝对值的话序列不具有单调性,所以我们用两个二分分别求最大的负值的绝对值以及最小的正值
- 求出来之后取较小的那个
// Problem: E. Klee's SUPER DUPER LARGE Array!!!
// Contest: Codeforces - Codeforces Round 971 (Div. 4)
// URL: https://codeforces.com/problemset/problem/2009/E
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// Submission Time: 2025-09-25 18:04:51int val(int num)//算下标num下的x值
{int xx=(k+k+num-1)*num/2;int yy=(k+k+n-1)*n/2-xx;return xx-yy;
}
bool check1(int mid)
{int kk=val(mid);if(kk<=0) return 1;return 0;
}
bool check2(int mid)
{int kk=val(mid);if(kk>=0) return 1;return 0;
}
void solve()//让差值小于0求最大,让差值大于0求最小
{cin >> n >> k;int l=1,r=n;int mx,mi;while(l<=r)//求出来的是下标{int mid=(l+r)/2;if(check1(mid)){l=mid+1;mx=mid;}else r=mid-1;}l=1,r=n;while(l<=r){int mid=(l+r)/2;if(check2(mid)){r=mid-1;mi=mid;}else l=mid+1;}int x=val(mi);int y=val(mx);y=abs(y);cout << min(x,y) << endl;
}
C - Cat, Fox and the Lonely Array
本题也是个二分, 就是涉及到了位运算的简单处理
-
区间越大越容易符合条件
-
左边界设为1,右边界为n
-
check函数:
- 先将初始区间的值处理出来
- 不断从左向右进行滑动
- 滑动过程中将最左边的OR值抛去,对右边的新值进行OR运算
-
如果窗口滑动时的所有OR值都一样就return 1,否则return 0;
// Problem: B. Cat, Fox and the Lonely Array
// Contest: Codeforces - Codeforces Round 945 (Div. 2)
// URL: https://codeforces.com/problemset/problem/1973/B
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// Submission Time: 2025-09-25 18:40:55bool check(int mid)
{if (mid==0) return 1;int cnt[31]={0};int cu=0; // 当前窗口的OR值for(int i=1; i<=mid; i++) // 初始化第一个窗口{int num = a[i];for(int j = 0; j <= 30; j++) // 遍历每个二进制位{ if(num&(1<<j)) // 第j位为1{ cnt[j]++;if(cnt[j]==1) cu|=(1<<j);// 第一次出现,OR值的这一位要置1}}}int an=cu;//目标值// 滑动窗口处理剩余元素for(int i=mid+1; i<=n; i++) // 移除左边离开窗口的元素{int l=a[i-mid];for(int j=0; j<=30; j++) {if(l&(1<<j)) {cnt[j]--;if (cnt[j] == 0) { // 该位在窗口中已消失,OR值的这一位要清0cu ^= (1 << j); // 第j位原本为1,异或后翻转为0}}}// 加入右边新元素int r=a[i];for(int j=0; j<=30; j++) {if(r&(1<<j)) {cnt[j]++;if(cnt[j]==1) cu|=(1 << j);}}// 检查当前窗口OR是否和目标一致if (cu != an) return 0;}return 1;
}
void solve()
{cin >> n;for(int i=1; i<=n; i++) cin >> a[i];int l=1,r=n;int ans;while(l<=r){int mid=(l+r)/2;if(check(mid)){ans=mid;r=mid-1;}else l=mid+1;}cout << ans << endl;
}
D - Ticket Hoarding
这题跟顺序没关系,直接贪心按题意模拟。赛时到最后都没想通为啥和顺序没关系。。
// Problem: D - Ticket Hoarding
// Contest: Virtual Judge - sd20250925训练赛
// URL: https://vjudge.net/contest/750667#problem/D
// Memory Limit: 1024 MB
// Time Limit: 1000 ms
// Submission Time: 2025-09-25 14:54:20void solve()
{cin >> n >> m >> k;for(int i=1; i<=n; i++) cin >> a[i];sort(a+1,a+1+n);int sum=0,xx=0;for(int i=1; i<=n; i++){if(k>=m){sum+=(a[i]+xx)*m;k-=m;xx+=m;}else{sum+=(a[i]+xx)*k;break;}}cout << sum << endl;
}
E - Turtle and an Infinite Sequence
-
根据OR运算的性质,ai−1∣ai∣ai+1a_{i-1} | a_i | a_{i+1}ai−1∣ai∣ai+1 会让每个元素都和旁边的元素进行或运算。
-
也就是说每秒中每个值的1都会向两边扩散一个单位
-
那么ana_nan在mmm秒之后的值就是它和左右两边相距mmm个单位的所有值进行或运算
-
也就是[max(0,n−m),n+m][max(0,n-m),n+m][max(0,n−m),n+m]区间内的所有值都进行或运算之后的值
-
nnn和mmm的范围都是1e91e91e9,如果直接一个个求的话肯定会超时
-
所以我们利用位运算的性质判断在这个区间中的各个位置是否有111
// Problem: E - Turtle and an Infinite Sequence
// Contest: Virtual Judge - sd20250925训练赛
// URL: https://vjudge.net/contest/750667#problem/E
// Memory Limit: 1024 MB
// Time Limit: 1000 ms
// Submission Time: 2025-09-25 19:18:30void solve()
{/*其实会发现,an的左右m个数的1会朝中间扩散我们只需要求[max(0,n-m),n+m]的OR值就行了*/cin >> n >> m;int L = max(0LL,n-m);int R = n + m;int ans = 0;for (int i=0; i<32; i++)// 如果L到R之间有任意数字在第i位是1,则ans的第i位设为1 {int cu = 1LL << i;if((L & cu) || (R& cu)) ans |= cu;//先看一下L或者R在这一位有没有1else//没有的话看中间有没有{int num = L | cu;if (num <= R) ans |= cu;}}cout << ans << endl;
}
F - Sakurako, Kosuke, and the Permutation
这题有点并查集的感觉,还比较绕
- 在排列中有某些元素可以通过互相交换变成"简单排列",我们把这些元素放到一起当做一个循环
- 一边遍历排列一遍进行交换,如果处理过的就标为1。
- 在同一个循环中需要交换(num-1)/2次
// Problem: E. Sakurako, Kosuke, and the Permutation
// Contest: Codeforces - Codeforces Round 981 (Div. 3)
// URL: https://codeforces.com/problemset/problem/2033/E
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// Submission Time: 2025-09-25 20:23:04void solve()
{vector<bool> v(N);cin >> n;for(int i=1; i<=n; i++) cin >> a[i];int ans=0;xfor(int i=1; i<=n; i++){if(!v[i]){int sum=0;int cu=i;while(v[cu]==0)//将本次循环中的所有元素都进行了遍历{sum++;v[cu]=1;//不断按照题目那样进行穿梭cu=a[cu];}ans+=(sum-1)/2;//比如此时为了一个长度为3的序列需要进行1次交换}}cout << ans << endl;
}
D - A and B
这道题赛时没想到好方法,打了很长的模拟,虽说造的样例都过了,但是明显不是正解,所以一直wa。
- 分别求让字母a和b聚到一起时的次数并取更小值
- 要让移动次数尽量小就要让两边的往中间的移
- 如果是偶数的话,中间值取第n/2和n/2+1个其实是一样的,那时候一直没想通还分开处理了
- 每一侧每多一个字母就可以少移动一次,这里用num来控制
// Problem: D. A and B
// Contest: Codeforces - Codeforces Round 1054 (Div. 3)
// URL: https://codeforces.com/contest/2149/problem/D
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// Submission Time: 2025-09-29 13:35:21#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int, int>
#define fi first
#define se second
#define endl '\n'
const int INF = 1e18;
const int N = 1e6+5;
// int a[N];
int n,m;
string s;
int cost(vector<int> v)
{int k=v.size();if(k%2) k++; k/=2;int sum=0;int num=0;for(int i=0; i<k; i++){num++;if(i==k-1) continue;sum+=abs(v[k-1]-v[i])-num;}num=0;for(int i=k; i<v.size(); i++){num++;if(i==k-1) continue;sum+=abs(v[k-1]-v[i])-num;}return sum;
}void solve()
{cin >> n >> s;vector<int> a,b;for(int i=0; i<n; i++){if(s[i]=='a') a.push_back(i);else b.push_back(i);}cout << min(cost(a),cost(b)) << endl;
}
signed main()
{ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);int T=1;cin >> T;while(T--) solve();return 0;
}
1300~1400涉及到二分、位运算等内容。题目经过简单挖掘之后比较干净,没什么复杂的背景。以后可以稍微涉及一点。