本场个人总结:本场感觉思维题为主,不过还是读错了题意,耽误了一些时间。
补题:总感觉F题好像是原题,但不记得是哪一题了。不过运气不错,补题的时候两发过了。
A. Min Max Swap
题意:给定两个长度为n的序列a和序列b,在两序列下标相同的位置可以进行交换。问两个序列中最大数的乘积最小是多少。
思路:乘积最小,直接将下标的两个值,小的给a序列,大的给b序列,此时两个序列中最大数的乘积最小。
#include
using namespace std;
const int N=105;
int n,a[N],b[N];
void solve(){
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]) swap(a[i],b[i]);
sort(a+1,a+n+1);
sort(b+1,b+n+1);
printf("%d\n",a[n]*b[n]);
}
int main(){
int t;scanf("%d",&t);
while(t--) solve();
}
B. Fun with Even Subarrays
题意:每次选择两个整数l和k,将 [ l + k , l + k + k − 1 ] [l+k,l+k+k-1] [l+k,l+k+k−1]中的数对应赋值给 [ l , l + k − 1 ] [l,l+k-1] [l,l+k−1],问是序列中所有数都相等的最少操作数。
思路:为了保证全部相等,又是从后往前赋值(覆盖),所以最后应该是一个全部又最后一个数 a [ n ] a[n] a[n]组成的序列。我们从后往前遍历,每次找出当前最长的连续相等子数组 l e n len len,将其向前赋值,这样从后往前相等的数的长度就有 2 ∗ l e n 2*len 2∗len,最后len>=n的时候就代表已经全部相等了。
#include
using namespace std;
const int N=2e5+5;
int n,a[N];
void solve(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int ci=0,len=0,pos=n;
while(1){
if(len>=n) break;
if(a[pos]==a[n]){len++;pos--;}
else {ci++;pos=n-2*len,len=2*len;}
}
printf("%d\n",ci);
}
int main(){
int t;scanf("%d",&t);
while(t--) solve();
}
C. And Matching
题意:t组数据,每组给你两个整数 n n n和 k k k, n n n一定是2的几次方。 [ 0 , n − 1 ] [0,n-1] [0,n−1]中 n n n个数要求分成 n / 2 n/2 n/2组,保证每组与的累加和等于k
思路:&的性质吧,首先我们需要知道KaTeX parse error: Expected 'EOF', got '&' at position 2: x&̲(n-1-x)=0,即 x x x和 n − 1 − x n-1-x n−1−x互为对应对(我瞎说的)
①:去掉特判 k = n − 1 k=n-1 k=n−1的情况,其中又分为 n = 4 、 k = 3 n=4、k=3 n=4、k=3和其他两种
/*
对于n=8 k=7
则取数区间[0,7],分成4组,我们单独看
十进制 二进制 对应n
7 111 n-1
6 110 n-2
5 101 n-3
2 010 2
1 001 1
0 000 0
我们要构成k=7,那么当我们选择((n-1)&(n-2))+((n-3)&1)=(7&6)+(5&1)=6+1=7 满足了和为7
此时7对应的0还没用,5对应的2还没用,刚好0&任何数都为0,剩下所有在依次数次对应对的总和也是0。符合条件
而对与n=4 k=3来说
十进制 二进制
3 11
2 10
1 01
0 00
不难发现当(n-3)就是1了,不能重复使用,所以不行输出-1
*/
②:剩下的我们直接KaTeX parse error: Expected 'EOF', got '&' at position 7: ((n-1)&̲(k))+((0)&(n-1-…刚好也是两个对应对,剩下直接对应对输出就行了。
因为避免k=0导致KaTeX parse error: Expected 'EOF', got '&' at position 7: ((n-1)&̲(k))=((0)&(n-1-…而输出两次,特殊输出k=0
#include
using namespace std;
const int N=(1<<16)+5;
int n,k;
void solve(){
scanf("%d%d",&n,&k);
if(n==4&&k==3){puts("-1");return;}
if(k==n-1){
printf("%d %d\n",n-1,n-2); //得n-2
printf("1 %d\n",n-3); //得1
printf("0 2\n"); //去掉已使用对应对的另一半
for(int i=3;i<n/2;i++) printf("%d %d\n",i,n-1-i);
return;
}
if(k==0){
for(int i=0;i<n/2;i++) printf("%d %d\n",i,n-1-i);
return;
}
printf("%d %d\n",n-1,k);
printf("0 %d\n",n-1-k);
for(int i=1;i<n/2;i++){
if(i==k||i==n-1-k) continue;
printf("%d %d\n",i,n-1-i);
}
}
D. Range and Partition
题意:将长度为n的序列分成k段,同时选取数值区间[x,y],保证每一段的里面在数值区间里面的个数严格大于在数值区间外的个数,使 y − x y-x y−x最小,输出x和y,已经划分区间的左右两端点
思路:二分,我们首先肯定得先确定选取数值区间长度,满足可以划分成k段,即该区间的数个数的两倍大于等于 n + k n+k n+k时,一定满足。随后枚举段数,cnt=当前段内区间内数的个数-区间外数的个数,当cnt>0且不是最后一段时就直接分为一段,cnt=0;最后剩下的所有数构成一段
#include
using namespace std;
const int N=2e5+5;
int n,k;
int a[N],b[N],pre[N];
vector<pair<int,int> >ans;
int cek(int x){
for(int i=1;i+x-1<=n;i++){
int num=pre[i+x-1]-pre[i-1];
if(2*num-n>=k) return i;
}
return -1;
}
void solve(){
ans.clear();
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) b[i]=pre[i]=0;
for(int i=1;i<=n;i++) scanf("%d",&a[i]),b[a[i]]++;
for(int i=1;i<=n;i++) pre[i]=pre[i-1]+b[i];
int l=0,r=n,pos=1;
while(l<r-1){
int mid=(l+r)>>1;
int st=cek(mid);
if(st!=-1) r=mid,pos=st;
else l=mid;
}
int cnt=0,ci=0,L=1;
for(int i=1;i<=n;i++){
if(pos<=a[i]&&a[i]<=pos+l) cnt++;
else cnt--;
if(cnt>0){
if(ci!=k-1){
ci++;
ans.push_back({L,i});
L=i+1;
cnt=0;
}
}
}
ans.push_back({L,n});
printf("%d %d\n",pos,pos+l);
for(auto it:ans) printf("%d %d\n",it.first,it.second);
}
int main(){
int t;scanf("%d",&t);
while(t--) solve();
}
E. Paint the Middle
题意:每次选三个下标 i , j , k i,j,k i,j,k,满足 i < j < k i
思路:想象一下,每次将 c [ j ] = 1 c[j]=1 c[j]=1都是夹在左右两边 a [ i ] a[i] a[i]相同的数,且 c [ i ] = 0 c[i]=0 c[i]=0。
那么我们贪心一下,记录好每个值对应的下标,从左到右开始,每次遇到还未使用过的a[i]且出现的次数len>=2,我们就可以使得 p o s [ a [ i ] ] [ 0 ] 到 p o s [ a [ i ] ] [ l e n − 1 ] pos[a[i]][0]到pos[a[i]][len-1] pos[a[i]][0]到pos[a[i]][len−1]之间所有下标c[j]=1,其中我们可能发现区间中有的数存在多个,且有一部分在区间外面,那么我们记录一下最右到达的地方就行了,原来区间可以全取,后面区间可以将原区间右端点起,到自己前一个位置全部选取,最后就是答案。
我的方法(dfs)去记录贡献,每次算贡献的时候看区间中数可以到达的最右端,然后如果大于原区间右端点的话,就再dfs
#include
using namespace std;
const int N=2e5+5;
int n,a[N],ans;
bool vis[N];
vector<int>pos[N];
void dfs(int l,int r){
ans+=r-l-1;
int mxr=r;
for(int i=l;i<r;i++){
vis[a[i]]=true;
int len1=pos[a[i]].size();
if(pos[a[i]][len1-1]>=mxr){
mxr=pos[a[i]][len1-1];
}
}
if(mxr>r) dfs(r,mxr);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
pos[a[i]].push_back(i);
}
for(int i=1;i<=n;i++){
int len=pos[a[i]].size();
int l=i,r=pos[a[i]][len-1];
if(!vis[a[i]]&&len>=2) dfs(l,r);
}
printf("%d\n",ans);
}
看rk1的解法,发现其实我们每次可以完成取数,就是将当前区间内所有数都取了,然后要是存在大于当前区间右端的点,我们找到最右端 m x r = m a x ( m x r , l s t [ a [ i ] ] ) mxr=max(mxr,lst[a[i]]) mxr=max(mxr,lst[a[i]]),同时不断再去完当前区间后,更新新的取数区间,区间的右端点 n o w = m x r now=mxr now=mxr
#include
using namespace std;
const int N=2e5+5;
int n,a[N],ans,lst[N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
lst[a[i]]=i;
}
int mxr=1,now=1;
for(int i=1;i<=n;i++){
mxr=max(mxr,lst[a[i]]);
if(i<now) ans++;
else now=mxr;
}
printf("%d\n",ans);
}
F. Flipping Range
题意:给定一个长度为n的序列a,和一个由m个数字组成的序列b,每次以序列b中包含的数字为区间长度,去序列a中选择区间,并将区间中的元素变成相反数。问多次操作后,最终序列a的所有元素总和的最大值是多少。
思路:经过发现,我们每次可以指定变换的区间长度为序列b中所有元素的gcd值=gd。
那么就可以得到一个结论,当sum为所有元素的绝对值之和时,记录一个下标 i i%gcd i为 [ 0 , g d ] [0,gd] [0,gd]区间的最小绝对值,多次操作后,最终会使得下标 [ 0 , g d − 1 ] [0,gd-1] [0,gd−1]这个区间每个的最小绝对值数构成的可变换序列c,而其他所有数都可以取绝对值。
此时序列c中元素的具体正负又由对应下标内负数个数决定,最后得到的正负数绝对值总和 n u 1 , n u 2 nu1,nu2 nu1,nu2,最后答案就是 s u m − 2 ∗ m i n ( n u 1 , n u 2 ) sum-2*min(nu1,nu2) sum−2∗min(nu1,nu2)。因为这是可变序列,所有在保证总和最大的情况下,要加得多减得少(使为正的多,为负的小)。那么就要在绝对值总和sum中减去负的和其原来的贡献,即 − 2 ∗ m i n ( n u 1 , n u 2 ) -2*min(nu1,nu2) −2∗min(nu1,nu2)
#include
using namespace std;
typedef long long ll;
const ll N=1e6;
ll n,m,a[N],b[N];
ll pos[N];
void solve(){
ll gd=0,sum=0;
scanf("%lld%lld",&n,&m);
for(ll i=0;i<n;i++) pos[i]=1e9;
for(ll i=0;i<n;i++) scanf("%lld",&a[i]),sum+=abs(a[i]);
for(ll i=1;i<=m;i++) scanf("%lld",&b[i]),gd=__gcd(gd,b[i]);
if(gd==1){printf("%lld\n",sum);return;}
for(ll i=0;i<n;i++){
pos[i%gd]=min(pos[i%gd],abs(a[i]));
if(a[i]<0&&i+gd<n) a[i+gd]=-a[i+gd];
}
ll nu1=0,nu2=0;
for(ll i=n-gd;i<n;i++){
if(a[i]<0) nu1+=pos[i%gd];
else nu2+=pos[i%gd];
}
printf("%lld\n",sum-2*min(nu1,nu2));
}
int main(){
int t;scanf("%d",&t);
while(t--) solve();
}