• tyvj P2020 Rainbow 的信号 【位运算】


    题目:

    Freda发明了传呼机之后,rainbow进一步改进了传呼机发送信息所使用的信号。由于现在是数字、信息时代,rainbow发明的信号用N个自然数表示。为了避免两个人的对话被大坏蛋VariantF偷听T_T,rainbow把对话分成A、B、C三部分,分别用a、b、c三个密码加密。现在Freda接到了rainbow的信息,她的首要工作就是解密。Freda了解到,这三部分的密码计算方式如下:
    在1~N这N个数中,等概率地选取两个数l、r,如果l>r,则交换l、r。把信号中的第l个数到第r个数取出来,构成一个数列P。
    A部分对话的密码是数列P的xor和的数学期望值。xor和就是数列P中各个数异或之后得到的数; xor和的期望就是对于所有可能选取的l、r,所得到的数列的xor和的平均数。
    B部分对话的密码是数列P的and和的期望,定义类似于xor和。
    C部分对话的密码是数列P的or和的期望,定义类似于xor和。

    分析:

    (如果你想AC的话,请忽略此部分)

    这个题的暴力其实很好打,直接进行无脑循环:(复杂度O(n^2)已经是优化过的了)

    40分代码:

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    long long n;
    long long a[100010];
    long long Xor[5000][5000],And[5000][5000],Or[5000][5000];
    double ans1=0,ans2=0,ans3=0; 
    long long cnt=0;
    
    long long read() {
        long long ret=0,flag=1;
        char ch=getchar();
        while(ch<'0' || ch>'9') {
            if(ch=='-')
                flag=-1;
            ch=getchar();
        }
        while(ch>='0' && ch<='9') {
            ret=ret*10+(long long)(ch-'0');
            ch=getchar();
        }
        return ret*flag;
    }
    
    
    int main()
    {
        n=read();
        cnt=n*n;
        for(int i=1;i<=n;i++)
            a[i]=read();
        memset(Xor,0,sizeof(Xor));
        memset(And,0,sizeof(And));
        memset(Or,0,sizeof(Or));
        for(int i=1;i<=n;i++)
        {
            Xor[i][i]=a[i];
            ans1+=Xor[i][i];
            And[i][i]=a[i];
            ans2+=And[i][i];
            Or[i][i]=a[i];
            ans3+=Or[i][i];
        }
        for(int i=n-1;i>=1;i--)
            for(int j=i+1;j<=n;j++)
                {
                    int p=(i+j)/2;
                    Xor[i][j]=Xor[i][p]^Xor[p+1][j];    
                    ans1+=Xor[i][j]*2;
                }
        for(int i=n-1;i>=1;i--)
            for(int j=i+1;j<=n;j++)
                {
                    int p=(i+j)/2;
                    And[i][j]=And[i][p]&And[p+1][j];    
                    ans2+=And[i][j]*2;
                }
        for(int i=n-1;i>=1;i--)
            for(int j=i+1;j<=n;j++)
                {
                    int p=(i+j)/2;
                    Or[i][j]=Or[i][p]|Or[p+1][j];    
                    ans3+=Or[i][j]*2;
                }
        printf("%.3lf %.3lf %.3lf
    ",ans1/cnt,ans2/cnt,ans3/cnt);
        return 0;
    }
    View Code

    而正解其实也不远了,通过分析这三种运算符的特点,只关心1的贡献:

    (^):

    1^1=0;

    0^1=1;

    他的功能就是:只要遇到1,就把当前的异或和取反,因此只要遇到1,就交换之前1和0的个数(即所有的1都变成了0,所有的0都变成了1)并且给1的个数加1;(自己应当拿笔模拟一下)

    (&):

    1&1=1;

    1&0=0;

    他的功能就是:只要遇到0,后面的都是0,因此只要找到前一个0出现的位置作为断点,计算有多少1出现就行了;

    (|):

    1|0=1;

    1|1=1;

    他的功能就是:只要遇到1,后面的都是1,因此只要找到前一个1出现的位置作为断点,计算有多少1出现就行了;

    下面是参考代码:

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cmath>
    #include <cstring>
    using namespace std;
    int n;
    int cnt[2],last_pos[2];
    int a[100005],b[100005];
    double ans_or=0,ans_xor=0,ans_and=0;
    
    void cal(int x)
    {
        fill(cnt,cnt+2,0);
        fill(last_pos,last_pos+2,0);
        for(int i=1;i<=n;i++) b[i]=((a[i]>>x)&1);
        for(int i=1;i<=n;i++)
        {
            if(i!=1)
            {
                ans_xor+=(double)(1<<x)/n/n*cnt[!b[i]];
                if (b[i]==0) ans_or+=(double)(1<<x)/n/n*last_pos[1];    
                else
                {
                    ans_or+=(double)(1<<x)/n/n*(i-1);
                    ans_and+=(double)(1<<x)/n/n*(i-1-last_pos[0]);
                }  
            }
            last_pos[b[i]]=i;
            if(b[i]==0) cnt[0]++;
            else
                swap(cnt[0],cnt[1]),cnt[1]++;
        }
        for(int i=1;i<=n;i++)
        if(b[i])
        {
            double tmp=(double)(1<<x)/n/n/2;
            ans_xor+=tmp;
            ans_or+=tmp;
            ans_and+=tmp;
        }
    }
    
    int main()
    {
        cin>>n;
        for(int i=1;i<=n;i++)
            cin>>a[i];
        for(int i=0;i<=30;i++) cal(i);
        printf("%.3lf %.3lf %.3lf",ans_xor*2,ans_and*2,ans_or*2);
        return 0;
    }
    View Code
  • 相关阅读:
    A. Playing with Paper
    手摇算法
    perl之创建临时文件夹遇到同名文件该咋办
    B. Two Buttons
    A Pangram
    shell的面试题
    A. Game
    B. Drazil and His Happy Friends
    A. Drazil and Date
    2道阶乘的算法题
  • 原文地址:https://www.cnblogs.com/linda-fcj/p/7218522.html
Copyright © 2020-2023  润新知