10.06 国庆节第九场模拟赛

密钥(key)

Description

在这个问题中,一个密钥是指一个长度为\(3n\)的二进制序列,其中\(n\)是正整数。
序列的每一位从左往右依次被编号为\(1\)\(3n\) ,一个密钥的权值是指数字不同的相邻位的个数再加上\(1\) 。比如:
\(000\) 的权值是 \(1\)\(011010100\) 的权值是 \(7\)

密钥可以被修改。确切地说,你可以不断地进行下面的操作:任选两个相邻的位,然后同时将它们取反。例如,可以通过一次操作把 \(000\) 修改为 110 。

给定一个长度为\(3n\)的密钥,请通过不超过\(n\)次操作,将其修改为一个权值不少于\(2n\)的密钥。你可以认为合法方案必然存在。

Input

输入一行,包含一个长度为\(3\)的倍数的\(01\)序列。

Output

第一行包含一个整数\(m\),表示操作的次数,你需要保证\(0 \leq m \leq n\)

第二行包含\(m\)个正整数 \(a_1,a_2,\dots,a_m(1 \leq a_i < n)\),依次表示每次翻转第\(a_i\) 和第\(a_{i+1}\)位。

如果初始密钥的权值已经不小于\(2n\)你可以仅输出一行一个整数\(0\)

xjb分析

玄学做法 \(50pts\)

​ 考虑到对答案有贡献的情况为当前位置与下一位置字符不同.

我们\(O(n)\)枚举位置,判断当前位置\(i\)是否与下一位置\(i+1\)相同.

如果不同,我们可以尝试翻转这两个位置,但是想要对答案有贡献?我们需要再判断\(i\)位置与\(i+2\)位置与\(i-1\)的字符是否存在相同,再决定我们是否去变换这两个位置.

例如 :

我们这时候改变\(i\)位置,\(i+1\)位置的话,

这时候,对答案的贡献就会变多\(+1\)

然后莫名其妙这样做就\(get\)\(50pts\)

正解

看到\(3n\),我们考虑三个的情况.

存在这些情况.

  1. \(000\)  5.\(111\)
  2. \(001\)  6.\(110\)
  3. \(011\)  7.\(100\)
  4. \(010\)  8.\(101\)

我们发现,

\(2.6\)情况翻转中间位置可以使贡献更大.

\(3.7\)情况翻转最左边位置可以使贡献更大.

这个可以手试一下.

因此代码中简单地模拟一下即可 qwq。

代码

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<string>
#include<cstring>
#define R register
using namespace std;
char s[300008];
int len,cnt,ans[300008],val;
int main()
{
//  freopen("key.in","r",stdin);
//  freopen("key.out","w",stdout); 
    scanf("%s",s+1);
    len=strlen(s+1);
    for(R int i=1;i<=len;i++)
        if(s[i]!=s[i-1] and i!=1)
            val++;
    if((val+1)>=2*(len/3))
    {
        puts("0");
        exit(0);
    }
    for(R int i=1;i<=len-2;i+=3)
    {
        if(s[i]=='0' and s[i+1]=='0' and s[i+2]=='1')
        {
            ans[++cnt]=i+1;
            s[i+1]='1';s[i+2]='0';
        }
        if(s[i]=='0' and s[i+1]=='1' and s[i+2]=='1')
        {
            ans[++cnt]=i;
            s[i]='1';s[i+1]='0';
        }
        if(s[i]=='1' and s[i+1]=='1' and s[i+2]=='0')
        {
            ans[++cnt]=i+1;
            s[i+1]='0';s[i+2]='1';
        }
        if(s[i]=='1' and s[i+1]=='0' and s[i+2]=='0')
        {
            ans[++cnt]=i;
            s[i]='0';s[i+1]='1';
        }
        if(s[i]=='1' and s[i+1]=='1' and s[i+2]=='1')
        {
            if(i==1)ans[++cnt]=i;
            else if(s[i-1]=='1')
            {
                ans[++cnt]=i;
                s[i]='0';
                s[i+1]='0';
            }
            else ans[++cnt]=i+1,s[i+1]='0',s[i+2]='1';
        }
        if(s[i]=='0' and s[i+1]=='0' and s[i+2]=='0')
        {
            if(i==1)ans[++cnt]=i;
            else if(s[i-1]=='0')
            {
                ans[++cnt]=i;
                s[i]='1';
                s[i+1]='1';
            }
            else ans[++cnt]=i+1,s[i+1]='1',s[i+2]='0';
        }
    }
    printf("%d\n",cnt);
    for(R int i=1;i<=cnt;i++)
        printf("%d ",ans[i]);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

最大公约数(gcd)

Description

Makik 是一名勤劳的学生。他在刚刚学完欧几里得算法时想多做一些练习题,于是在纸上写下了一个长度为\(n\)的排列。之后,他做了\(m\)轮练习。对于第\(i\)轮练习,他从排列中挑出一个区间\([l_i,r_i]\) ,并对处于这个区间中的元素两两求最大公约数,再找出这些最大公约数中的最大值。

Makik 已经成为最大公约数的大师了,而且仁慈地把这个练习方法介绍给了你。现在,请你也来做做看

Input

第一行包含两个数字\(n,m\),分别表示排列的长度和练习的轮数。
接下来一行\(n\)个数字,依次表示这个排列中的每个元素。
再接下来\(m\)行,其中第\(i\)行包含两个数字\(l_i,r_i\),表示在第\(i\)轮练习中挑出的区间。

Output

输出\(m\)行,每行一个数字,表示该轮练习里区间内的元素两两间最大公约数中的最大值。

xjb分析

暴力

 对于\(m\)次询问,直接枚举\([l_i,r_i]\)区间的数,两两求\(gcd\)\(max\).

代码写法大致如下:

for(int i=1;i<=m;i++)
{
    scanf("%d%d",&l,&r);
    int now=0;
    for(int j=l;j<=r;j++)
        for(int k=j+1;k<=r;k++)
            now=max(now,gcd(a[j],a[k]));
}

时间复杂度为\(O(m\times n^2 loga_i)\)

令人感到mmp的是,暴力部分全部\(TLE\)

正解

考虑到对答案有贡献的是这些数的约数.

因此我们对这些约数搞事。

做法

 对每个数求出约数,在一个区间内出现超过两次的约数,那么它肯定是某两个数的\(gcd\).

 这样我们把所有出现次数\(\geq 2\)的约数加入线段树中维护最大值,并每次更新即可.

但是,如果在线做的话依旧不行.

  我们考虑将询问离线.

如何离线?

 考虑到我们必须要知道整个区间的数的约数的情况,所以我们对询问的右端点排序.

我们从小到大对右端点\(r_i\)进行排序,当遇到某个右端点的时候,输出ans即可.

代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cctype>
#define ls o<<1
#define rs o<<1|1
#define N 100005
#define R register
using namespace std;
inline void in(int &x)
{
    int f=1;x=0;char s=getchar();
    while(!isdigit(s)){if(s=='-')f=-1;s=getchar();}
    while(isdigit(s)){x=x*10+s-'0';s=getchar();}
    x*=f;
}
struct cod{
    int l,r,idx;
    bool operator <(const cod&a)const
    {
        return r<a.r;
    }
}que[100008];
int ans[N];
int n,m,a[N],tr[N<<3],exist[N];
inline void up(int o){tr[o]=max(tr[ls],tr[rs]);}
void build(R int o,R int l,R int r)
{
    if(l==r)
    {
        tr[o]=0;
        return;
    }
    int mid=(l+r)>>1;
    build(ls,l,mid);
    build(rs,mid+1,r);
    up(o);
}
void change(int o,int l,int r,int pos,int del)
{
//  printf("%d %d %d %d %d",o,l,r,pos,del);
    if(l==r){tr[o]=max(tr[o],del);return;}
    int mid=(l+r)>>1;
    if(pos<=mid)change(ls,l,mid,pos,del);
    else change(rs,mid+1,r,pos,del);
    up(o);
}
int query(int o,int l,int r ,int x,int y)
{
//  printf("%d %d %d %d %d\n",o,l,r,x,y);
    if(x<=l and r<=y)return tr[o];
    int mid=(l+r)>>1,res=0;
    if(x<=mid)res=max(res,query(ls,l,mid,x,y));
    if(y>mid)res=max(res,query(rs,mid+1,r,x,y));
    return res;
}
int main()
{
 // freopen("gcd.in","r",stdin);
//  freopen("gcd.out","w",stdout);
    in(n),in(m);
    {
        for(R int i=1;i<=n;i++)in(a[i]);
        build(1,1,n);
        for(R int i=1;i<=m;i++)
            in(que[i].l),in(que[i].r),que[i].idx=i;
        sort(que+1,que+m+1);
        int now=1;
        for(R int i=1;i<=n;i++)
        {
            for(R int j=1;j*j<=a[i];j++)
            {
                if(a[i]%j==0)
                {
                    if(exist[j])change(1,1,n,exist[j],j);//判断之前是否存在过
                    if(exist[a[i]/j])change(1,1,n,exist[a[i]/j],a[i]/j);//判断之前是否存在过
                    exist[j]=i,exist[a[i]/j]=i;
                }
            }
            while(i==que[now].r and now<=m)
            {
                ans[que[now].idx]=query(1,1,n,que[now].l,que[now].r);
                now++;
            }
        }
        for(R int i=1;i<=m;i++)printf("%d\n",ans[i]);
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/-guz/p/9748068.html