Codeforces Round #768 (Div. 2) (ABCDE)
本场个人总结:本场感觉思维题为主,不过还是读错了题意,耽误了一些时间。
 A. Min Max Swap
 
 题意:给定两个长度为n的序列a和序列b,在两序列下标相同的位置可以进行交换。问两个序列中最大数的乘积最小是多少。
 思路:乘积最小,直接将下标的两个值,小的给a序列,大的给b序列,此时两个序列中最大数的乘积最小。
#include<bits/stdc++.h>
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],问是序列中所有数都相等的最少操作数。
题意:每次选择两个整数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<bits/stdc++.h>
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<bits/stdc++.h>
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,已经划分区间的左右两端点
题意:将长度为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<bits/stdc++.h>
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<j<k
      
     
    i<j<k,相同KaTeX parse error: Expected 'EOF', got '&' at position 17: …[i]=c[j]=c[k]=0&̲&a[i]=a[k],那么我们可以使得
    
     
      
       
        c
       
       
        [
       
       
        j
       
       
        ]
       
       
        =
       
       
        1
       
      
      
       c[j]=1
      
     
    c[j]=1,问最后最多有多少下标pos的
    
     
      
       
        c
       
       
        [
       
       
        p
       
       
        o
       
       
        s
       
       
        ]
       
       
        =
       
       
        1
       
      
      
       c[pos]=1
      
     
    c[pos]=1。
题意:每次选三个下标
    
     
      
       
        i
       
       
        ,
       
       
        j
       
       
        ,
       
       
        k
       
      
      
       i,j,k
      
     
    i,j,k,满足
    
     
      
       
        i
       
       
        <
       
       
        j
       
       
        <
       
       
        k
       
      
      
       i<j<k
      
     
    i<j<k,相同KaTeX parse error: Expected 'EOF', got '&' at position 17: …[i]=c[j]=c[k]=0&̲&a[i]=a[k],那么我们可以使得
    
     
      
       
        c
       
       
        [
       
       
        j
       
       
        ]
       
       
        =
       
       
        1
       
      
      
       c[j]=1
      
     
    c[j]=1,问最后最多有多少下标pos的
    
     
      
       
        c
       
       
        [
       
       
        p
       
       
        o
       
       
        s
       
       
        ]
       
       
        =
       
       
        1
       
      
      
       c[pos]=1
      
     
    c[pos]=1。
 思路:想象一下,每次将
    
     
      
       
        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<bits/stdc++.h>
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<bits/stdc++.h>
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);
}










