Codeforces Round #768 (Div. 2)

Posted on 2022-02-03 17:17  Capterlliar  阅读(34)  评论(0编辑  收藏  举报

A. Min Max Swap

题意:给出两个数组,每次可以交换下标相同的两个数。交换次数不限,求两个数组中最大值乘积的最小值。

解:一开始想的是排个序,求第n个和第2n个数的乘积。但可能有两个较大数相同,所以只能遍历一遍,把大的全换到a,求出相对最小值。

代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define maxx 1005
#define maxn 1005
#define maxm 200005
#define eps 0.00000001
#define inf 0x7fffffff
#define mod 998244353
//#define int long long
int n;
int a[maxx],b[maxx];
signed main() {
    int T;
    scanf("%d",&T);
    while(T--){
        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]);
    }
    return 0;
}
View Code

B. Fun with Even Subarrays

题意:给出一个数组。每次可以选择一个长度为偶数的区间,使左边一半等于右边一半。求使得整个数组相同的最小次数。

解:开始看反了。。。由于是左边等于右边,所以从右边开始遍历,碰到不相同的pia过去。

代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define maxx 200005
#define maxn 1005
#define maxm 200005
#define eps 0.00000001
#define inf 0x7fffffff
#define mod 998244353
//#define int long long
int n;
int a[maxx];
signed main() {
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        int cnt=1,ans=0;
        int i=n-1;
        while(i>0){
            if(a[i]==a[n]) {
                cnt++;
                i--;
            }
            else{
                cnt*=2;
                i=n-cnt;
                ans++;
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}
View Code

C. And Matching

题意:给出n和k,要求将0到n-1的数两两配对,其与运算之和为k。保证n为2的幂。

解:怎么简单怎么来。显然 i 与 n-i-1 与之和为0。现在留出k,那么要给n-k-1找对象(?,使得它们与出来是0。0显然满足这个要求。现在留下n-1和k,n-1和k与完还是k,完美。考虑到k=n-1时不能这么干,那多拉两个和它们配对,最终在只要对称即可。n-1和n-2可以整出k-1,现在留下0和1要整出1,不够,那拉上n-3和2。n-3的末尾显然是1,问题解决。这样需要6个数,也就是n=4,k=3时无解,特判即可。

代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define maxx 45
#define eps 0.00000001
#define inf 0x7fffffff
#define mod 998244353
//#define int long long
int n,k;
int vis[70005];
signed main() {
    int T;
    scanf("%d",&T);
    while(T--){
        for(int i=0;i<n;i++)
            vis[i]=0;
        scanf("%d%d",&n,&k);
        if(n==4){
            if(k==0)
                printf("0 3\n1 2\n");
            else if(k==1)
                printf("1 3\n0 2\n");
            else if(k==2)
                printf("0 1\n2 3\n");
            else if(k==3)
                printf("-1\n");
            continue;
        }
        if(k==0){
            for(int i=0;i<n/2;i++)
                printf("%d %d\n",i,n-1-i);
            continue;
        }
        if(k==n-1){
            printf("%d %d\n",n-1,n-2);
            printf("%d %d\n",1,n-3);
            printf("2 0\n");
            for(int i=3;i<n/2;i++)
                printf("%d %d\n",i,n-i-1);
            continue;
        }
        printf("%d %d\n",k,n-1);
        printf("%d %d\n",n-k-1,0);
        for(int i=1;i<n/2;i++){
            if(i==k||n-i-1==k)
                continue;
            printf("%d %d\n",i,n-i-1);
        }
    }
    return 0;
}
View Code

D. Range and Partition

题意:要求将n个数分为k组,每组中在范围[x,y]内的数严格大于不在其中的数。现给出n个数,求x和y以及对应的分割方案,并要求y-x之差最小。

解:第一遍读题的时候读成了组内在范围的数大于组外不在范围内的数,想这怎么搞。看了样例恍然大悟。考虑到得到x和y后很好分组,决定二分,先考虑二分y-x,但很难搞。于是考虑枚举x和y,套路地枚举左端点,二分右端点,O(n)验证。但这样复杂度变成了O(n2logn),会T。优化验证,通常到了这时候可不可行能直接算出来。有k个区间,每个区间贪心地让范围内的比范围外的至少多一个,一共多k个。这k个不管怎么分布凑合凑合好像都能分出来。问题解决。

代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define maxx 200005
#define eps 0.00000001
#define inf 0x7fffffff
#define mod 998244353
//#define int long long
int n,k;
int a[maxx];
int num[maxx]={0};
signed main() {
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        for(int i=0;i<=n;i++)
            num[i]=0;
        for(int i=1;i<=n;i++)
            num[a[i]]++;
        for(int i=1;i<=n;i++)
            num[i]+=num[i-1];
        int x=1,y=n;
        for(int i=1;i<=n;i++){
            int l=i,r=n,mid,ans=inf;
            while(l<=r){
                mid=(l+r)/2;
                int in=num[mid]-num[i-1],out=n-in;
                if(in-out>=k)
                    ans=mid,r=mid-1;
                else
                    l=mid+1;
            }
            if(ans!=inf&&ans-i<y-x)
                x=i,y=ans;
        }
        printf("%d %d\n",x,y);
        int cnt=0,cnt1=0,cnt2=0,s=1;
        for(int i=1;i<=n;i++){
            if(x<=a[i]&&a[i]<=y)
                cnt1++;
            else
                cnt2++;
            if(cnt1>cnt2){
                if(cnt==k-1){
                    printf("%d %d\n",s,n);
                    break;
                }
                printf("%d %d\n",s,i);
                s=i+1;
                cnt++;
                cnt1=cnt2=0;
            }
        }
    }
    return 0;
}
View Code

*E. Paint the Middle

题意:给出1-n的一组数,每次选出相同且未上色的两个数,然后可以给它们之间任何一个数上色。求最多上色个数。

解:研究了一下是个区间问题。有若干个端点不同的区间,边沿上色了里面就不能上色。区间不重叠时很好解,那么现在考虑重叠的情况。有2个重叠时,要删3个点;3个重叠时,要删4个点。总结一下,每个区间的头删掉,加上最后一个的尾巴,完美,开始写代码。先整理出线段,统计有几个重叠,更新答案,提交!然后,

QAQ

最后绷不住了去看tutorial,发现思路是一样的,研究了半天发现统计有几个重叠线段时会加入不必要的条数,比如:

 

              红色标注的那条是不必要的,但如果按左端点排序统计线段条数,更新右边界,会把它算进去。

那么现在问题就是在当前区间中找到右端点最大的,我屈服了,看了status里的写法。大意就是每次更新右边界,判断当前点是否在第一个区间内,如果是ans++,超出区间进入直接跑到右端点,orz

代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define maxx 200005
#define eps 0.00000001
#define inf 0x7fffffff
#define mod 998244353
//#define int long long
int n,k;
int a[maxx];
int s[maxx]={0},e[maxx]={0};
vector<pair<int,int> > seg;
signed main() {
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
        e[a[i]]=i;
    int ans=0,l=0,r=0;
    for(int i=1;i<=n;i++){
        r=max(r,e[a[i]]);
        if(l>i)
            ans++;
        else
            l=r;
    }
    printf("%d\n",ans);
    return 0;
}
View Code

F. Flipping Range

题意:给出数组a和b,每次可以从b里挑选一个数x,将a中任意一个长为x的子数组正负号反转。操作次数不限,求a数组所有数之和最大值。

解:首先将b的操作化为一种操作,即每次改变gcd(b[1]...b[m])长度的子数组,然后考虑怎样使和最大。最理想的情况是所有负的变成整的。考虑如何达到这种情况。设gcd值为g,则每次改变g个数。将a按照模g的值分为g组,每进行一次操作,每组都有刚好一个数改变,如果最后每组改变的数量相同,那么这种情况是可达的。这样共有2g种情况,还是很大,考虑压缩,用它们的异或值0或1代表其状态。其实挺难想的,证明也是找到结论再证,那记一下吧。然后就可以用dp[i][0/1]表示余数为i,异或和为0/1时最大值。

代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define maxx 1000005
#define maxn 1005
#define maxm 200005
#define eps 0.00000001
#define inf 0x7fffffff
#define mod 998244353
//#define int long long
int n,m;
ll a[maxx],b[maxx];
ll dp[maxx][2];
ll gcd(ll a,ll b){
    return b==0?a:gcd(b,a%b);
}
signed main() {
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%lld",&a[i]);
        for(int i=1;i<=m;i++)
            scanf("%lld",&b[i]);
        ll g=0;
        for(int i=1;i<=m;i++)
            g=gcd(g,b[i]);
        for(int i=0;i<=g-1;i++)
            dp[i][0]=0,dp[i][1]=-inf;
        for(int i=1;i<=n;i++){
            int t=i%g;
            ll t0=max(dp[t][0]+a[i],dp[t][1]-a[i]);
            ll t1=max(dp[t][0]-a[i],dp[t][1]+a[i]);
            dp[t][0]=t0;dp[t][1]=t1;
        }
        ll sum0=0,sum1=0;
        for(int i=0;i<=g-1;i++){
            sum0+=dp[i][0];
            sum1+=dp[i][1];
        }
        printf("%lld\n",max(sum0,sum1));
    }
    return 0;
}
View Code