Codeforces Round #768 (Div. 2) ABCDEF代码+题解
很有价值的一场比赛(完整题面链接:Codeforces Round #768 (Div. 2)),遂写下此篇。代码包含头文件OrbitZore/ATL/base/include/cardinal.hpp
A. Min Max Swap
题意
给定长度为的两个数组和,你可以任意执行下面的操作
- 选定一个,交换和。
求的最小值
解法
因为乘法满足单调性,则
要使最小,考察和中的最大值,其显然会在最终计算乘法时出现。
因为和有对称性,不妨令出现在中,我们现在要做的就是尽可能缩小使最小。
因为操作之间无关,贪心即可。
代码
int main(){
int T;cin>>T;
while (T--){
int n;vec<int> a[2];
cin>>n>>rtar(a[0],n)>>rtar(a[1],n);
using T=pair<int,bool>;
auto [ma,j]=max({
T{*max_element(all(a[0])),0},
T{*max_element(all(a[1])),1}
});
for (int i=0;i<n;i++)
if (a[j][i]<a[!j][i])
swap(a[j][i],a[!j][i]);
int mb=*max_element(all(a[!j]));
cout << ma*mb << endl;
}
}
B. Fun with Even Subarrays
题意
给定长度为的数组,你可以任意执行下面的操作。
- 选定数组的一个在出开始长度为的子序列 (),对于每一个,令
比如对于,选定,结果是。
求使整个数组所有元素相等的最小操作次数。
解法
不难发现最终相等的数字一定是。接下来贪心倒着执行操作即可。注意等于的情况。
代码
int main(){
int T;cin>>T;
while (T--){
int n;
vec<int> v;
cin>>n>>rtar(v,n);
int l=n-1,r=n-1,c=0;//(l,r]为所有元素相等的区间
while (l>=0){
while (l>=0&&v[l]==v[r]) l--;
if (l<0) break;
l-=(r-l);
c++;
}
cout<<c<<endl;
}
}
C. And Matching
题意
给定一个元素的集合,。
在中找对元素,满足
- ,在对中只出现过一次
- 其,为位并运算
输出任意一个解,如果无解,输出
解法
考虑,对于所有,生成即可。
考虑,在上述方案中将和中的和交换即可。
考虑,先使用上一个方案凑出即,然后凑出,交换中的到任意一个中的即可。
考虑到时无法进行此操作,考察这种情况。此时相当于在中选两个数位并,观察到,其结果。没有答案。
代码
using uint32=unsigned int;
int main(){
int T;
cin>>T;
while (T--){
uint32 n,k;
cin>>n>>k;
unsigned int li=n-1;
if (n==4&&k==3){
cout << -1 << endl;
continue;
}
vec<pair<uint32,uint32>> v(n/2);
for (int i=0;i<n/2;i++)
v[i]={i,li&~i};
if (k!=li){
if (k<n/2) swap(v[k].x,v[0].x);
else swap(v[li&~k].y,v[0].x);
}else{
swap(v[1].y,v[0].x);
swap(v.back().y,v.end()[-2].y);
}
for (auto& [a,b]:v)
cout<<a<<" "<<b<<"\n";
}
}
D. Range and Partition
题意
给定一个长度为的数组,将其分割成个子序列,满足每个子序列中,元素的数量严格大于的数量。要求最小化,求一个分割方案。
解法
考虑到要比较数量,不妨设,每个子序列中。
显然关于单调,考察固定下能分割子序列数量的最大值。
观察一个子序列,如,其一定可以更细分,增加分割子序列数量。下证
- 如果:显然。
- 如果,即:因为所有的。考察前缀和,。由离散观点上的连续性知。拆分成即可。
所以在取最大值时,其每个子区间,,每个子区间构成了数组的分割,。
现在考虑固定,问题变成了求,使数量为。
观察到增加时,也增加,考虑双指针法求出每一个对应的。
现在求出了,考虑怎么构造子序列。
既然已知,固定住,循环,如果条件成立就立即建立子序列。但是考虑到过程中可能会超过,还需要将最后一个区间手动建立。
代码
int main(){
int T;cin>>T;
while (T--){
int n,k;
vec<int> v;
cin>>n>>k>>rtar(v,n);
vec<int> sv=v;
sort(all(sv));
int vl=0,vr=INF;
auto dnext=[&](int r){
while (r+1<n&&sv[r]==sv[r+1]) r++;
++r;
return r;
};
auto Dnext=[&](int l,int r){
while (r<n&&r-l-n+(r-l)<k){
r=dnext(r);
}
return r;
};
for (int l=0,r=0;l<n;l=dnext(l)){
r=Dnext(l,r);
if (r-l-n+(r-l)>=k){
if (vr-vl>sv[r-1]-sv[l]){
vr=sv[r-1];
vl=sv[l];
}
}
}
print("{} {}\n",vl,vr);
int b=0,cnt=1;
for (int i=0,c=0;i<n;i++){
c+=vl<=v[i]&&v[i]<=vr?1:-1;
if (c==1&&cnt<k){
cnt++;
print("{} {}\n",b+1,i+1);
c=0;
b=i+1;
}
}
if (cnt<=k){
print("{} {}\n",b+1,n);
}
}
}
E. Paint the Middle
题意
给定一个长度为的数列和色块。开始时所有。你可以进行以下操作。
- 选中三个元素,满足,令。
求的最大值。
解法
如果,满足。那么可以不管。(区间包含)
有了这个结论,我们就可以对于每一个,考察和即可。
如果,满足。那么可以令变成1。令还是变成1都是一样的,因为对两个区间包含的元素的操作我们可以在令还是变成1之前进行。对于两个区间之外的,则不影响。不妨就令。(区间交叉)
将视角放回在数组上,尝试循环设置。我们首先根据区间包含,考虑维护一个最长区间,如果点在区间内则设置;而根据区间交叉,循环到时一定要将代入最长区间并设置。但是这样会被设置,我们不想要设置,拿个变量记录即可。
代码
int main(){
int n;
vec<int> v;
cin>>n>>rtar(v,n);
vec<int> lst(n+2,0);
for (int i=0;i<n;i++) lst[v[i]]=i;
int mlst=-1,pre=-1,cnt=0;//mlst即最长区间,pre即记录r1,cnt为计数
for (int i=0;i<n;i++){
cmax(mlst,lst[v[i]]);
if (pre<=i) pre=mlst;
else cnt++;
}
cout<<cnt<<endl;
}
F. Flipping Range
题意
给定一个长度为的数组和个正整数()的集合。可以对数组执行下面的操作:
- 选中一个
- 在中选中长度为的区间,对其上每一个数乘以。正式地¥¥,即选定,,对于所有令。(翻转)
求的最大值。
解法
观察到,对于数组的两个元素,可以有这两种组合,长度为的区间和长度为的区间共用一个起点;共用一个终点。起点的组合可以使这些区间翻转,终点的组合可以使这些区间翻转。其长度均为,前者起点为,后者起点为,因为所有,所以。其结果是组合等价于产生新的集合元素。考虑更相减损法,其等价于集合中只有一个元素。
定义为对一个元素为区间起点的翻转次数,因为重复翻转没有意义,所以。
现在我们要处理的区间长度是固定的,那么不难发现都是一个定值(表示异或),其等于总翻转次数模2。
定义状态函数,意义为数组中取得最大值的情况。
对于每个都可能翻转,更新状态即可。
初始化为。因为后者必须要有操作。
转移方程见代码。
代码
int main(){
int T;cin>>T;
while (T--){
int n,m,g=0;vec<int> v,z;
cin>>n>>m>>rtar(v,n)>>rtar(z,m);
for (auto i:z) g=gcd(i,g);
vec<array<ll,2>> dp(g,{0,(ll)MINF/2});
for (int i=0;i<n;i++){
int rem=i%g;
tie(dp[rem][0],dp[rem][1])=tuple(
max(dp[rem][0]+v[i],dp[rem][1]-v[i]),
max(dp[rem][0]-v[i],dp[rem][1]+v[i])
);
}
ll sum0=0,sum1=0;
for (int i=0;i<g;i++){
sum0+=dp[i][0];
sum1+=dp[i][1];
}
cout << max(sum0,sum1) << endl;
}
}