Codeforces Round #780 (Div. 3)简训

导语

菜菜菜菜
在这里插入图片描述

涉及的知识点

树状数组,思维

链接:Codeforces Round #780 (Div. 3)

题目

A Vasya and Coins

题目大意:有 a a a个1和 b b b个2,找出一个最小的数 s s s,使得这 a a a个1与 b b b个2任意的和不能表达出来 s s s

思路:特判一下有没有1,如果没有1那就是所有之和+1,因为在一定范围内所有数字都可以用二进制表示,而1其实是20

代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5;
int t,n,a[maxn];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>t;
    while(t--) {
        int a,b;
        cin >>a>>b;
        if(a==0) {
            cout <<1<<endl;
            continue;
        }
        cout <<2*b+a+1<<endl;
    }
    return 0;
}

B Vlad and Candies

题目大意: n n n种糖果,每种有数量,每天吃一个数量最多的,询问是否能够与前一天吃的种类不同

思路:直接判断数量最多的是不是比次多的至少多2即可

代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5;
int t,n,a[maxn];
char s[maxn];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>t;
    while(t--) {
        int n;
        cin >>n;
        for(int i=1; i<=n; i++)cin >>a[i];
        sort(a+1,a+1+n);
        if(a[n]>a[n-1]+1)cout <<"NO\n";
        else cout <<"YES\n";
    }
    return 0;
}

C Get an Even String

题目大意:定义一个字符串为偶满足下列两个条件:

  1. 字符串长度为 n n n
  2. 对于一个奇数 i ( 1 ≤ i ≤ n − 1 ) , a i = a i + 1 i(1\le i\le n-1),a_i=a_{i+1} i(1in1)ai=ai+1

现在给出一个由小写字母组合的字符串,移除一些字符能够使得字符串为偶,求出移除字符的最小个数

思路:一开始总想着删除哪些元素来获得,这样去考虑就会变得复杂,因为删除了一个元素之后会改变序列下标的奇偶性,每次删除一个就考虑一次的话难以实现并且时间开销很大

正难则反,既然考虑删除不行,那就考虑配对,如果将所有元素两两配对,删除其余不配对的元素,那么元素间的奇偶关系自然就满足了

代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
int t;
bool vis[30];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>t;
    while(t--) {
        string s;
        cin >>s;
        int len=s.length(),ans=0;
        memset(vis,0,sizeof(vis));
        for(int i=0; i<len; i++) {
            int pos=s[i]-'a';
            if(vis[pos]) {
                vis[pos]=0;
                ans+=2;//记录配对数
                memset(vis,0,sizeof(vis));//更新标记数组
            } else
                vis[pos]=1;
        }
        cout <<len-ans<<endl;//需要删除的数量
    }
    return 0;
}

D Maximum Product Strikes Back

题目大意:给出一个包含 n n n个整数的序列 a a a,对于每一个下标 i ( 1 ≤ i ≤ n ) i(1\le i\le n) i(1in)保证 − 2 ≤ a i ≤ 2 -2\le a_i\le 2 2ai2,可以从数组的开头和结尾移除任意数量的元素(可以是0,也可以删除整个数组),判断从数组开头和结尾各移除多少个元素,使得剩余元素的积最大,输出开头移除和结尾移除的个数,默认空数组的积为1

思路:根据题设条件,所获得的答案中必不能包括0,那么可以以每个0位分界线,判断所获得的区间的最值是多少,由于区间长度很长,有效的数字其实只有2,所以可以直接统计区间内-2,2的数量即可,对于一个区间来说,如果负数个数为偶,那么答案就是区间内2,-2的数量,如果负数的个数为奇,就需要考虑左删除还是右删除了,贪心的想,只删除损失最小的一边即可,不需要删除两边,为了使损失最小,只删到最左/右负数即可

代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5;
int t,n,a[maxn],cnt;
struct node {
    int l,r,posr,posl,ans;
    //区间左右端点,最右负数下标,最左负数下标,可用2数量
    bool def;//标记奇数数量是否为偶
    bool operator<(const node&a) {//根据区间内的可用2的数量排序
        return ans>a.ans;
    }
} e[maxn<<1];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>t;
    while(t--) {
        cin >>n;
        cnt=0;
        int l=0,r=0,posr=0,posl=0,ans=0;
        bool def=0;//对应每个区间的临时变量
        for(int i=1; i<=n; i++)
            cin >>a[i];
        for(int i=1; i<=n; i++) {
            if(a[i]!=0) {//如果不为0,不用分割
                if(l==0)l=i;//第一次赋值
                if(a[i]<0) {
                    def^=1;
                    if(a[i]==-2)ans++;//可用2
                    if(posl==0)//最左负数
                        posl=i;
                    posr=i;//最右负数
                }
                if(a[i]==2)ans++;//可用2
                r++;//更新右端点
                if(i!=n)continue;//防止没有0的情况,把最后一个区间录入
            }
            e[++cnt].l=l;
            e[cnt].r=r;
            e[cnt].ans=ans;
            e[cnt].posl=posl;
            e[cnt].posr=posr;
            e[cnt].def=def;
            //录入区间相关信息
            def=0;
            l=i+1,r=i;//r=i保证每次拓宽时为右端点
            posl=0,posr=0;
            ans=0;
            //初始化
        }
        for(int i=1; i<=cnt; i++)
            if(e[i].def) {//如果奇数个负数
                int d1=0,d2=0;
                for(int j=e[i].r; j>=e[i].posr; j--)
                    if(abs(a[j])==2)
                        d1++;//损失2数量
                for(int j=e[i].l; j<=e[i].posl; j++)
                    if(abs(a[j])==2)
                        d2++;
                if(d1>d2) {//判断是去掉左边还是右边
                    e[i].l=e[i].posl+1;
                    e[i].ans-=d2;//不要写反了
                } else {
                    e[i].r=e[i].posr-1;
                    e[i].ans-=d1;
                }
            }
        sort(e+1,e+1+cnt);
        if(e[1].ans==0)//特判无2的情况
            cout <<n<<" "<<0<<endl;
        else
            cout <<e[1].l-1<<" "<<n-e[1].r<<endl;
    }
    return 0;
}

E Matrix and Shifts

题目大意:给出一个 n × n n×n n×n的01矩阵 A A A,给出四种移动操作

  1. 循环上移
  2. 循环下移
  3. 循环左移
  4. 循环右移

执行若干次移动操作后,可以若干次挑选矩阵中一个元素将其异或1,每次执行这个异或操作花费为1,但是移动操作无花费,询问使得矩阵为主对角矩阵的最小花费

思路:无论是向哪边移动,格子元素间的相对位置是不会变的,为了使得花费最小,那么最好一开始就能使得主对角线上全部为1,如图,这三个元素能在移动中组成主对角线,那么找到所有能够组成主对角线的“链”,判断1数量最多的那条链1的个数是多少,剩余的1直接变0,不够的0直接变1,统计花费即可
在这里插入图片描述

代码

#include <bits/stdc++.h>
//#define int long long
using namespace std;
const int maxn=2121;
int t,n,cnt;
bool maze[maxn][maxn];
bool vis[maxn][maxn];
int main() {
//    ios::sync_with_stdio(0);
//    cin.tie(0);
    cin >>t;
    while(t--) {
        cin >>n;
        int zero=0,one=0,mx=0;
        for(int i=1; i<=n; i++)
            for(int j=1; j<=n; j++) {
                int x;
                scanf("%1d",&x);//只扫1位
                maze[i][j]=x;
                x?one++:zero++;//记录1个数
                vis[i][j]=0;//输入的时候顺带清空
            }
        for(int i=1; i<=n; i++)
            for(int j=1; j<=n; j++) {
                if(vis[i][j])continue;
                vis[i][j]=1;
                int tmp=0;
                for(int k=0; k<n; k++) {
                    int x=i+k>n?i+k-n:i+k,y=j+k>n?j+k-n:j+k;
                    //这里不能取模,取模就会出现0
                    vis[x][y]=1;//标记已经访问过了
                    if(maze[x][y])tmp++;//记录可用1的个数
                }
                mx=max(mx,tmp);
            }
        cout <<one+n-2*mx<<endl;
    }
    return 0;
}

F1 Promising String(easy version)

题目大意:定义一个字符串为平衡当前仅当字符串内的加减号个数,定义一个字符串为可变的当前且仅当如果执行若干次替换操作后,字符串可以变成平衡(替换操作:将两个相邻的减号替换成一个加号),现在给出一个字符串 s s s,判断 s s s有多少个子串是可变的, 1 ≤ s t r l e n ( s ) ≤ 3000 1\le strlen(s)\le3000 1strlen(s)3000

思路:可以找到这样一个规律,对于一个区间,如果-号比+至少多3两个,那么一定存在两个相邻-,如果进行一次替换,+和-的差值就增加了3,那么,对于每个区间,判断是否-比+正好多3的倍数个(0也算),如果多3的倍数,一定能通过替换使得区间内±互等

代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=3e3+5;
int n,pre[maxn],t;
char s[maxn];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>t;
    while(t--) {
        cin >>n;
        cin >>s+1;
        int len=strlen(s+1),ans=0;
        for(int i=1; i<=len; i++)
            if(s[i]=='+')pre[i]=pre[i-1]-1;//遇到+,-1
            else pre[i]=pre[i-1]+1;
        for(int i=1; i<=len-1; i++)
            for(int j=i+1; j<=len; j++)
                if((pre[j]-pre[i-1])%3==0&&pre[j]>=pre[i-1])//-比+多并且正好是3的倍数
                    ans++;
        cout <<ans<<endl;
    }
    return 0;
}

F2 Promising String(hard version)

题目大意:略,和F1一样,只是数据范围扩大

思路:规律同F1一样,但是数据范围变大,就不能暴力获得区间,提取F1的条件,对于一对下标 i , j i,j i,j,如果对应位置前缀和后者大于前者,并且差值为3的倍数,那么这个区间就可行,等价于在模3情况下,i,j对应前缀和相等,那么可以使用树状数组统计前缀和,对于一个位置i,前面是否存在已经出现的同余项前缀和,如果存在,代表可以凑出一个合法区间,由于树状数组下标不能为0,所以需要将坐标整体进行偏移,具体见代码

代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e6+10;
int n,sum[maxn],t;
int tree[maxn][3];
char s[maxn];
void update(int x,int c) {
    for(int i=x; i<=maxn; i+=i&(-i))
        tree[i][c]++;
}
int query(int x,int c) {
    int res=0;
    for(int i=x; i>=1; i-=i&(-i))
        res+=tree[i][c];
    return res;
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>t;
    while(t--) {
        cin >>n;
        cin >>s+1;
        int ans=0,mn=0;
        sum[0]=0;
        for(int i=1; i<=n; i++) {
            if(s[i]=='+')sum[i]=sum[i-1]-1;//遇到+,-1
            else sum[i]=sum[i-1]+1;
            mn=min(mn,sum[i]);//获得最小值,便于偏移
        }
        for(int i=0; i<=n-mn+10; i++)//清空
           tree[i][0]=tree[i][1]=tree[i][2]=0;
        for(int i=0; i<=n; i++)sum[i]-=mn-1;
        for(int i=0; i<=n; i++) {
            int c=sum[i]%3;//找同余项
            ans+=query(sum[i],c);//统计在其前面出现的同余项的个数
            update(sum[i],c);//更新
        }
        cout <<ans<<endl;
    }
    return 0;
}

参考文献

  1. Tutorial
  2. Codeforces Round #780 (Div. 3) A~F2 题解
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值