Codeforces Round #779 (Div. 2)简训

导语

两道,菜

涉及的知识点

数学,位运算,思维

链接: Codeforces Round #779 (Div. 2)

题目

A Marin and Photoshoot

题目大意:给出一个01串,要求任何一个连续的长度大于2的区间内1的个数大于等于0的个数,判断需要插入的最小字符个数

思路:贪心的考虑,直接对每个0之间的长度,如果小于3则直接插入1,特判整个串为1的情况

代码

#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--) {
        cin >>n;
        string s;
        cin >>s;
        if(n==1) {//特判长度为1
            cout <<0<<endl;
            continue;
        }
        vector<int>pos;
        int len=s.length(),ans=0;
        for(int i=0; i<len; i++)//收录所有0的位置
            if(s[i]=='0')pos.push_back(i);
        if(pos.size())
            for(int i=0; i<pos.size()-1; i++)//获得需要插入的个数
                if(pos[i+1]-pos[i]<=2)ans+=3-pos[i+1]+pos[i];
        cout <<ans<<endl;
    }
    return 0;
}

B Marin and Anti-coprime Permutation

题目大意:定义一个好排列满足: g c d ( 1 × p 1 , 2 × p 2 , … , n × p n ) > 1 gcd(1×p_1,2×p_2,\dots,n×p_n)>1 gcd(1×p1,2×p2,,n×pn)>1,给出 n n n,判断 1 1 1 n n n的排列中有多少个好排列

思路:打表可以得到,奇数结果为0,偶数为 n / 2 n/2 n/2之间的平方积

代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5;
const int mod=998244353;
int t,n;
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>t;
    while(t--) {
        cin >>n;
        if(n&1) {//奇数都是0
            cout <<0<<endl;
            continue;
        }
        int res=1;
        for(int i=2;i<=n;i+=2)//平方的积
            res=((res*i/2)%mod*i/2)%mod;
        cout <<res<<endl;
    }
    return 0;
}

C Shinju and the Lost Permutation

题目大意:给出一个初始排列 p p p,定义第 i i i次循环操作后的结果使得 [ p 1 , p 2 , … , p n ] [p_1,p_2,\dots,p_n] [p1,p2,,pn]变成 [ p n − i + 1 , … , p n , p 1 , p 2 , … , p n − i ] [p_{n-i+1},\dots,p_n,p_1,p_2,\dots,p_{n-i}] [pni+1,,pn,p1,p2,,pni],定义 p p p序列的值为不同长度前缀下的最大值,例如对于序列 [ 1 , 2 , 5 , 4 , 6 , 3 ] [1,2,5,4,6,3] [1,2,5,4,6,3],可以得到一个 b b b序列: [ 1 , 2 , 5 , 5 , 6 , 6 ] [1,2,5,5,6,6] [1,2,5,5,6,6],进行一次循环操作后可以得到一个新的 b b b序列: [ 2 , 5 , 5 , 6 , 6 , 6 ] [2,5,5,6,6,6] [2,5,5,6,6,6],现在对于每个 b b b序列,存在一个数 c c c,那么 n n n次操作就有一个序列 c c c c c c是每一次操作后得到的 b b b序列中数字的种类个数例如前面例子就可以得到 c c c的一部分: [ 4 , 3 ] [4,3] [4,3],现在给出 c c c序列,判断是否存在原序列 p p p能够满足 c c c序列

思路:题意较难理解,理清楚之后思路可能会好很多,首先对于 c c c序列,有且只有一个1,因为一定存在一次操作,使得序列最大值作为 p 1 p_1 p1,那么构造出来的 b b b就会全是最大值,判断给出的 c c c如果1的个数不为1,那么一定无法构造
那么,把 c c c为1的位置作为 c 1 c_1 c1进行直接调换,这样就可以得到每次操作之后 c i c_i ci的变化了,讨论相邻两项 c i , c i + 1 c_i,c_{i+1} ci,ci+1的情况,如果 c i + 1 − c i > 1 c_{i+1}-c_i>1 ci+1ci>1,代表从后面调到前面一个数使得p中最长递增子序列的长度增加了2,这显然是不可能的,因为最多只会增加1,如果 c i + 1 − c i = 1 c_{i+1}-c_i=1 ci+1ci=1,代表增加1,这是可以构造的,如果 c i + 1 − c i = 0 c_{i+1}-c_i=0 ci+1ci=0,代表不变,这也是可以构造的,相当于 p n p_n pn代替了最长递增子序列中的最小项,如果 c i + 1 − c i < 0 c_{i+1}-c_i<0 ci+1ci<0,也可以构造,代表代替了最长递增子序列中的前 c i − c i + 1 c_i-c_{i+1} cici+1项,判断 c i c_i ci c i + 1 c_{i+1} ci+1大小关系即可

代码

#include <bits/stdc++.h>
//#define int long long
using namespace std;
const int maxn=1e5+50;
int t,n;
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>t;
    while(t--) {
        cin >>n;
        vector<int>c(n);
        for(int &x:c)cin >>x;
        if(count(c.begin(),c.end(),1)!=1) {
            //如果有超过一个位置有1,或者没有1的位置则一定是错的
            cout <<"NO\n";
            continue ;
        }
        int pos=find(c.begin(),c.end(),1)-c.begin();
        //找到1的位置
        rotate(c.begin(),c.begin()+pos,c.end());
        //以1为边界,交换两个区间,把1换到前面来
        bool flag=1;
        for(int i=1; i<n; ++i)
            if(c[i]-c[i-1]>1) {//判断是否存在一个大于2
                cout <<"NO\n";
                flag=0;
                break;
            }
        if(flag)cout <<"YES\n";
    }
    return 0;
}

D1 388535 (Easy Version)

题目大意:给出两个整数 l , r l,r l,r,给出一个长度为 r − l + 1 r-l+1 rl+1的排列: [ l , l + 1 , … , r ] [l,l+1,\dots,r] [l,l+1,,r],对每个数异或一个 x x x,给出异或之后的结果,求 x x x保证l=0

思路:由于保证了左边界为0,那么可以保证异或一个数之后与之前每一位上0与1的和为定值,因此可以统计异或前与异或后的每一位上的01,出现变化则代表x这一位为1了
当然Hard难度下两个代码也可以过,毕竟是更一般的情况

代码

#include <bits/stdc++.h>
//#define int long long
using namespace std;
const int maxn=2e5+5;
int t,l,r,cnt[20],res[20];
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>t;
    while(t--) {
        cin >>l>>r;
        int ans=0;
        memset(cnt,0,sizeof(cnt));
        memset(res,0,sizeof(res));
        for(int i=l; i<=r; i++) {
            int x;
            cin >>x;
            for(int j=0; j<=17; j++)//提取初始序列每一位上的1
                if(i>>j&1)cnt[j]++;
            for(int j=0; j<=17; j++)//提取结果序列每一位上的1
                if(x>>j&1)res[j]++;
        }
        for(int j=0; j<=17; j++)//判断1的个数是否有变化
            if(res[j]==(r-l+1)-cnt[j])ans|=(1<<j);
        cout <<ans<<endl;
    }
    return 0;
}

D2 388535 (Hard Version)

题目大意:给出两个整数 l , r l,r l,r,给出一个长度为 r − l + 1 r-l+1 rl+1的排列: [ l , l + 1 , … , r ] [l,l+1,\dots,r] [l,l+1,,r],对每个数异或一个 x x x,给出异或之后的结果,求 x x x保证l≥0

思路:有两种思路,一种是通过找规律得到的,一种是通过字典树

异或规律:对于连续区间中的一对相邻数字 2 p , 2 p + 1 2p,2p+1 2p,2p+1有这样的一个规律: 2 p ⊕ 2 p + 1 = 1 2p⊕2p+1=1 2p2p+1=1,那么可以将给定区间内的数字两两配对获得定值,匹配会出现下列几种情况

  1. l l l奇, r r r奇,如 1 , 2 , 3 , 4 , 5 1,2,3,4,5 1,2,3,4,5,匹配完后会剩下 l ⊕ x l⊕x lx,那么与 l l l异或后得到 x x x,具体做法是去掉所有的匹配,这样剩下来的就是 l ⊕ x l⊕x lx
  2. l l l奇, r r r偶, l ⊕ x , r ⊕ x l⊕x,r⊕x lx,rx都无法匹配,两个值都尝试一下
  3. l l l偶, r r r奇,则都可以匹配,则 x x x的末尾可0可1(尝试异或0或1只是结果顺序不同),将所有的数右移一位,重复匹配
  4. l l l偶, r r r偶,和情况1类似

字典树:对于异或问题,首先由二进制来考虑,设异或的结果为数组 b b b,原数组为 a a a,暴力的思路是枚举一个 x x x来求异或最大值或者最小值,并且分别判断是否与 r , l r,l r,l相等,为什么要这样判断?显然, b b b中的数字各不相同,根据异或的性质,每个数异或上 x x x之后,还原的序列里的数字也各不相同,所以只需要满足异或的最大值与最小值对应相等即可
暴力枚举值域内的 x x x显然不行,这个时候可以利用异或的性质,枚举每个 b i ⊕ l b_i⊕l bil,因为 b i = a i ⊕ x b_i=a_i⊕x bi=aix b i b_i bi一定有一个 l ⊕ x l⊕x lx,最后会剩下 x x x,异或式子为 a i ⊕ x ⊕ b i ⊕ l = l a_i⊕x⊕b_i⊕l=l aixbil=l

代码(异或规律)

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5;
int t,l,r;
set<int>s,tmp;
bool judge(int x) {
    for(auto i:s)
        if((i^x)<l||(i^x)>r)return 0;
    return 1;
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>t;
    while(t--) {
        cin >>l>>r;
        s.clear();
        for(int i=l; i<=r; i++) {//保存所有的数字
            int x;
            cin >>x;
            s.insert(x);
        }
        int ans=1;
        while(!s.empty()) {
            tmp=s;
            for(auto i:s)//去掉
                tmp.erase(i^1);
            if((l&1)&(r&1)) {//都是奇数
                ans*=(*tmp.begin()^l);
                break;
            }
            if((l&1)&!(r&1)) {//奇和偶
                if(judge(*tmp.begin()^l))//暴力判断
                    ans*=(*tmp.begin()^l);
                else
                    ans*=(*tmp.begin()^r);
                break;
            }
            if(!(l&1)&!(r&1)) {//如果都是偶
                ans*=(*tmp.begin()^r);
                break;
            }
            //偶和奇,最后一位可0可1,按照0解决
            tmp.clear();
            for(auto i:s)
                tmp.insert(i>>1);
            s=tmp;
            l>>=1,r>>=1,ans<<=1;
        }
        cout <<ans<<endl;
    }
    return 0;
}

代码(字典树)

#include <bits/stdc++.h>
//#define int long long
using namespace std;
const int maxn=5e5+50;
int t,l,r,trie[maxn][2],tot,a[maxn];
void Insert(int x) {
    int p=1;
    for(int i=17; i>=0; i--) {
        bool k=x&(1<<i);
        if(!trie[p][k]) {
            trie[p][k]=++tot;
            trie[tot][0]=trie[tot][1]=0;//初始化,防止先前的记录干扰
        }
        p=trie[p][k];
    }
}
int GetMax(int x) {
    int p=1,res=0;
    for(int i=17; i>=0; i--) {
        bool k=x&(1<<i);
        if(trie[p][k^1]) {//走反路径,这样才能得到最大的异或值
            res+=1<<i;
            p=trie[p][k^1];
        } else
            p=trie[p][k];//如果没有就只能走原数字,因为走到根才是完整的数字
    }
    return res;
}
int GetMin(int x) {
    int p=1,res=0;
    for(int i=17; i>=0; i--) {
        bool k=x&(1<<i);
        if(trie[p][k])//走当前路径,获得最小异或值
            p=trie[p][k];
        else {
            res+=1<<i;//给这一个赋值
            p=trie[p][k^1];
        }
    }
    return res;
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >>t;
    while(t--) {
        cin >>l>>r;
        tot=1;
        trie[1][0]=trie[1][1]=0;//注意初始化,全部清零会超时
        for(int i=1; i<=r-l+1; i++) {
            cin >>a[i];
            Insert(a[i]);
        }
        for(int i=1; i<=r-l+1; i++)
            if(GetMax(a[i]^l)==r&&GetMin(a[i]^l)==l) {//判断最大值和最小值
                cout <<(a[i]^l)<<endl;
                break;
            }
    }
    return 0;
}

参考文献

  1. Tutorial
  2. 11. 388535
  3. 388535 (Hard Version) (字典树)
  4. Codeforces Round #779 (Div. 2) D2. 388535 (Hard Version)「字典树 」
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值