Codeforces #275 Div 1 简要题解

  • Post author:
  • Post category:其他


比赛总结

这次比赛打得总算像样了。。。

做了A和B,都是wa了两次才ac,罚时有点惨

在正式和非正式选手里排名537名,在正式选手里排名450名

A. Diverse Permutation

题目链接


http://codeforces.com/contest/482/problem/A

题目大意

要你构造一个








1


,


2


,


.


.


.


n











的排列








a


[


]











,使得最终










|




a


[


i


]





a


[


i


+


1


]




|













的互不相同的值恰好有








k











思路

显然,











|




a


[


i


]





a


[


i


+


1


]




|














的互不相同的值最多时,构造的排列一定是

1








n











2









n





1












3








n





2











因此,我们可以先构造排列的前








K













个数字,这部分数字满足 1









n












2








n





1











3








n





2











…的特点,这样就出现了








K







1











种。然后剩下的部分,我们就单调递增或递减填入没填过的数字,这样就出现了第








K













种取值,也就是1










K




=


1












时特判下就好了

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 1100000

using namespace std;

int n,K;
int tot=0;

int main()
{
    scanf("%d%d",&n,&K);
    if(K==1)
    {
        for(int i=1;i<=n;i++) printf("%d ",i);
        printf("\n");
        return 0;
    }
    K--;
    int a=1,b=n+1;
    printf("1 ");
    int i=2;
    for(int t=1;t<=(K-1);t++,i++)
        printf("%d ",(i&1)?++a:--b);
    bool flag=(i&1)?true:false;
    for(;i<=n;i++)
        if(flag) printf("%d ",++a);
        else printf("%d ",--b);
    printf("\n");
    return 0;
}

B. Interesting Array

题目链接


http://codeforces.com/contest/482/problem/B

题目大意

要你构造一个长度为








n











的数列,满足









m












个要求。对于每个要求,要使得








[





L






i







,





R






i







]











区间内的数列数字的与和为








v


a


l










思路

我们首先讨论








v


a


l





1











的情况

注意到,一个区间内的数列数字的与和为








1











,就必须满足这个区间里所有数字都是1,而一个区间内的数列数字的与和为









0












,只需要满足这个区间里有一个数字是0即可。假设一个与和为1的区间








[





L






i







,





R






i







]











和一个与和为0的区间








[





L













i







,





R













i







]











有交集,那么相交的那部分肯定是取1。

这个特点非常类似于或运算:重叠的那部分先是区间或了1,然后是区间或了0,最终得到了连续的一段1,于是问题转变为:给你一个初始全为0的序列,每次区间或某个数字,然后离线对每个询问检查一遍,每个询问对应的区间








[





L






i







,





R






i







]











的与和是否为








v


a


l











。并且要输出最终的序列

这个问题非常简单,直接维护一个支持区间修改、区间查询的线段树即可。维护区间或的lazy tag、以及区间与和标记。最终遍历一遍线段树的所有叶子节点即可

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 110000

using namespace std;

int or_tag[MAXN<<2],andsum_tag[MAXN<<2]; //区间或标记,区间与和标记

void pushup(int o)
{
    andsum_tag[o]=andsum_tag[o<<1]&andsum_tag[o<<1|1];
}

void pushdown(int o)
{
    or_tag[o<<1]|=or_tag[o];
    andsum_tag[o<<1]|=or_tag[o];
    or_tag[o<<1|1]|=or_tag[o];
    andsum_tag[o<<1|1]|=or_tag[o];
    or_tag[o]=0;
}

void update(int o,int L,int R,int ql,int qr,int orval)
{
    if(ql<=L&&R<=qr)
    {
        andsum_tag[o]|=orval;
        or_tag[o]|=orval;
        return;
    }
    pushdown(o);
    int M=(L+R)>>1;
    if(ql<=M) update(o<<1,L,M,ql,qr,orval);
    if(qr>M) update(o<<1|1,M+1,R,ql,qr,orval);
    pushup(o);
}

int query(int o,int L,int R,int ql,int qr)
{
    if(ql<=L&&R<=qr)
    {
        return andsum_tag[o];
    }
    pushdown(o);
    int M=(L+R)>>1,ans=1; //mlgb!!!不能拿ans=1然后and答案,这样的话可能会错!
    if(qr<=M) return query(o<<1,L,M,ql,qr);
    if(ql>M) return query(o<<1|1,M+1,R,ql,qr);
    //cout<<"L: "<<L<<" R: "<<R<<" ans: "<<ans<<endl;
    return query(o<<1,L,M,ql,qr)&query(o<<1|1,M+1,R,ql,qr);
}

void print(int o,int L,int R)
{
    if(L==R)
    {
        printf("%d ",andsum_tag[o]);
        return;
    }
    pushdown(o);
    int M=(L+R)>>1;
    print(o<<1,L,M);
    print(o<<1|1,M+1,R);
}

struct Query
{
    int L,R,val;
}querys[MAXN];

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&querys[i].L,&querys[i].R,&querys[i].val);
        update(1,1,n,querys[i].L,querys[i].R,querys[i].val);
    }
    for(int i=1;i<=m;i++)
    {
        if(query(1,1,n,querys[i].L,querys[i].R)!=querys[i].val)
        {
            printf("NO\n");
            return 0;
        }
    }
    printf("YES\n");
    print(1,1,n);
    return 0;
}

C. Interesting Array

题目链接


http://codeforces.com/contest/482/problem/C

题目大意

n个长为m的字符串,A会先从中随机选择一个串,让B去判断A选择的串是哪个串,每次B会随机地知道m位字母里未知的一位,问B确定答案的期望步数

思路

我们可以先预处理出








f




[


S




]


=




















m











个下标中已经知道了集合









S














里的下标的概率,以及








c


n


t


[


S




]


=




















m











个下标中已经知道了集合









S














里的下标,会找出多少个相同的字符串(只能找出一个字符串,就是0)。

答案就是















f




[


S




]


c


n


t


[


S




]







n





















为什么呢?假设A不是随机选取一个串,而是指定了一个串。假如当前B已知的字符的下标集合为








S













,若









c


n


t


[


S




]


=


0












,表明B同学不需要再询问了,直接结束,否则








c


n


t


[


S




]


>

=



2











,会找出至少2个暂时看起来一样的字符串,B同学至少还需要再走一步,这样的话步数会+1,对期望步数的答案的贡献为








f




[


S




]





1










而如果A是指定了一个串呢?最终的期望步数就是






















n








i


=


1




































i






































































n






















,我们可以直接利用上一段的做法来求出


















n








i


=


1




































i




















































































c


n


t


[


S




]


>

=



2











时,会找出至少2个暂时看起来一样的字符串,B同学至少还需要再走一步,这样的话,对于这








c


n


t


[


S




]











个字符串,要将它们一一区分开来,每个字符串的判断步数都会+1,因此对期望步数之和的答案的贡献为








f




[


S




]





c


n


t


[


S




]











代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 60

using namespace std;

typedef long long int LL;

int n,m;
char s[MAXN][MAXN];
LL cnt[1<<22]; //cnt[S]=集合S里每个下标对应字母均相同的字符串集合
double f[1<<22]; //f[S]=已经知道了集合S里的下标对应位置的字母的概率

int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
        scanf("%s",s[i]);
    m=strlen(s[0]);
    for(int i=0;i<n;i++) //这里重复枚举也没事,|运算重复一次的话结果不会改变
        for(int j=0;j<n;j++)
            if(i!=j)
            {
                int tmp=0;
                for(int k=0;k<m;k++)
                    if(s[i][k]==s[j][k])
                        tmp|=(1<<k);
                cnt[tmp]|=((LL)1<<(LL)j);
            }
    for(int S=(1<<m)-1;S>=0;S--) //cnt[S],cnt[S'],若S'是S子集,则cnt[S']|=cnt[S]
        for(int i=0;i<m;i++)
            if(S&(1<<i))
                cnt[S^(1<<i)]|=cnt[S];
    double ans=0;
    f[0]=1;
    for(int S=0;S<(1<<m);S++)
    {
        int tot=0; //tot=集合S里已经确定的下标个数
        for(int i=0;i<m;i++)
            if(S&(1<<i))
                tot++;
        for(int i=0;i<m;i++)
            if(!(S&(1<<i)))
            {
                f[S|(1<<i)]+=f[S]/(m-tot);
            }
        for(int i=0;i<n;i++)
            if(cnt[S]&((LL)1<<(LL)i))
                ans+=f[S];
    }
    printf("%.10lf\n",ans/(double)n);
    return 0;
}



版权声明:本文为qpswwww原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。