• 【SRM20】数学场


    第一题

    n个m位二进制,求异或值域总和。

    【题解】异或值域--->使用线性基,解决去重问题。

    m位二进制--->拆位,每位根据01数量可以用组合数快速统计总和。

    #include<cstdio>
    #include<cstring>
    #include<cctype>
    #include<cmath>
    #include<bitset>
    #include<algorithm>
    #define ll long long
    using namespace std;
    int read(){
        char c;int s=0,t=1;
        while(!isdigit(c=getchar()))if(c=='-')t=-1;
        do{s=s*10+c-'0';}while(isdigit(c=getchar()));
        return s*t;
    }
    /*------------------------------------------------------------*/
    const int inf=0x3f3f3f3f,maxn=410,MOD=1e9+7;
    
    int n,m,fac[maxn],fav[maxn],f2[maxn],sum,ans;
    char s[maxn];
    bitset<maxn>a[maxn],b[maxn];
    void gcd(int a,int b,int &x,int &y){
        if(!b){x=1;y=0;}
        else{gcd(b,a%b,y,x);y-=x*(a/b);}
    }
    int inv(int a){
        int x,y;
        gcd(a,MOD,x,y);
        return ((x%MOD)+MOD)%MOD;
    }
    int C(int n,int m){return 1ll*fac[n]*fav[m]%MOD*fav[n-m]%MOD;}
    int main(){
        n=read();m=read();
        for(int i=1;i<=n;i++){
            scanf("%s",s+1);
            for(int j=1;j<=m;j++)a[i][m-j]=s[j]-'0';
            for(int j=m-1;j>=0;j--)if(a[i][j]){
                if(b[j]==0){b[j]=a[i];break;}
                else a[i]^=b[j];
            }
        }
        int sum0,sum1;
        fac[0]=1;for(int i=1;i<=m;i++)fac[i]=1ll*fac[i-1]*i%MOD;
        for(int i=0;i<=m;i++)fav[i]=inv(fac[i]);
        f2[0]=1;for(int i=1;i<=m;i++)f2[i]=1ll*f2[i-1]*2%MOD;
        ans=0;
        for(int i=0;i<m;i++){
            sum0=sum1=sum=0;
            for(int j=0;j<m;j++)if(b[j]!=0){
                if(b[j][i]==0)sum0++;else sum1++;
            for(int j=1;j<=sum1;j+=2)sum=(sum+C(sum1,j))%MOD;
            ans=(ans+1ll*f2[i]*sum%MOD*f2[sum0]%MOD)%MOD;
        }
        printf("%d",ans);
        return 0;
    }
    View Code

    第二题

    给定n个数,求从中任意选数的所有方案gcd的总和。n个数字都<=m(给定)。n<=10^5,m<=10^6。

    【题解】对于每个数字a(1<=a<=m)处理出n个数中有多少个是它的倍数,记为b,那么有它是2^b-1种方案的公因数,再容斥掉其倍数(ans[j])得到ans[i]。

    使用的仍是自带容斥的技巧,就是直接容斥掉已经计算过的答案ans,这些答案ans已经自带上一层容斥了。

    复杂度分析:1枚举n次,2枚举n/2次,所以总共枚举n*(1+1/2+1/3+1/4+...+1/n),后面的数列是常见的近似ln(n),所以总复杂度O(n ln n)。

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int maxn=1000010,MOD=1e9+7;
    
    int n,m,f2[maxn],a[maxn],b[maxn],ans[maxn],ANS=0;
    int main(){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)scanf("%d",&a[i]),b[a[i]]++;
        f2[0]=1;for(int i=1;i<=m;i++)f2[i]=f2[i-1]*2%MOD;
        for(int i=m;i>=1;i--){
            int num=0;
            for(int j=i;j<=m;j+=i)num+=b[j];
            ans[i]=f2[num]-1;
            for(int j=i+i;j<=m;j+=i)ans[i]=(ans[i]+MOD-ans[j])%MOD;
            ANS=(ANS+1ll*i*ans[i]%MOD)%MOD;
        }
        printf("%d",ANS);
        return 0;
    }
    View Code

    第三题

    给定n(n<=2000),1~n任意排列,进行如下操作:①若有序,停止。②发现连续一段+1,并在一起不再分开。③再次随机排列。

    求停止前进行③的期望次数。

    【题解】

    期望问题直接考虑递推,f[i]表示1~i任意排列的期望次数,得到初步方程f[i]=1/i!*f[1]+?/i!*(f[j]+1),j表示剩余j块,?就是全排列中剩余j块的排列个数。

    令a[i][j]表示1~i排列中共j块的排列数,得到方程f[i]=∑a[i][j]*(f[j]+1)/i!,j=2~i,把右边的f[i]移位后得f[i]=a[i][j]*(f[j]+1)/(i!-a[i][i])。(i=j时,f[j]暂时为0,就会+a[i][i])

    接下来的问题是求a[i][j](j<i),因为一块就是连续一段,那么j块可以视为1~i(有序)的i-1个间隔中中放j-1个隔板,然后把隔出来的j段,视为j个数排列中形成j块的数量。

    a[i][j]=a[j][j]*C(i-1,j-1),j<i

    特别地,a[i][i]=i!-∑a[i][j],j=1~i-1。

    过程中要记得,n个数排列形成n块的方案是a[n][n]而不是1,才不会出错!

    #include<cstdio>
    #include<cstring>
    #include<cctype>
    #include<cmath>
    #include<algorithm>
    #define ll long long
    using namespace std;
    int read(){
        char c;int s=0,t=1;
        while(!isdigit(c=getchar()))if(c=='-')t=-1;
        do{s=s*10+c-'0';}while(isdigit(c=getchar()));
        return s*t;
    }
    int min(int a,int b){return a<b?a:b;}
    int max(int a,int b){return a<b?b:a;}
    int abs(int x){return x>0?x:-x;}
    void mins(int &a,int b){if(a>b)a=b;}
    void maxs(int &a,int b){if(a<b)a=b;}
    //void insert(int u,int v){tot++;e[tot].v=v;e[tot].from=first[u];first[u]=tot;}
    /*------------------------------------------------------------*/
    const int inf=0x3f3f3f3f,MOD=1e9+7,maxn=2010;
    
    void gcd(ll a,ll b,ll& d,ll& x,ll& y){
        if(!b){d=a;x=1;y=0;}
         else{gcd(b,a%b,d,y,x);y-=x*(a/b);}
    }
    ll inv(ll a,ll n){
        ll d,x,y;
        gcd(a,n,d,x,y);
        return (x%n+n)%n;
    }
    int n;
    ll fac[maxn],fav[maxn],f[maxn],a[maxn][maxn];
    ll C(ll n,ll m){return fac[n]*fav[m]%MOD*fav[n-m]%MOD;}
    int main(){
        fac[0]=1;fav[0]=1;
        for(int i=1;i<=2001;i++)fac[i]=fac[i-1]*i%MOD;
        for(int i=1;i<=2001;i++)fav[i]=inv(fac[i],MOD);
        n=read();
        for(int i=1;i<=n;i++){
            a[i][i]=fac[i];
            for(int j=1;j<i;j++){
                a[i][j]=C(i-1,j-1)*a[j][j]%MOD;
                a[i][i]=(a[i][i]+MOD-a[i][j])%MOD;
            }
        }
        f[1]=0;
        for(int i=2;i<=n;i++){
            int o=0;
            for(int j=2;j<=i;j++)o=(o+a[i][j]*(f[j]+1))%MOD;//diao yong le ben shen
            f[i]=o*inv((fac[i]+MOD-a[i][i])%MOD,MOD)%MOD;
        }
        printf("%lld",f[n]);
        return 0;
    }
    View Code
  • 相关阅读:
    Python 编程入门(2):复杂数据类型(列表,字典)
    Python 编程入门(1):基本数据类型
    编程的智慧总结笔记
    学习 Vim 命令总结
    JS中如何使用radio
    关于模板页调用js的问题
    关于session认证用户名和密码的父类(简单认证)
    如何使用日期格式化函数
    数据库中怎么查询所有的表名
    简单的分页
  • 原文地址:https://www.cnblogs.com/onioncyc/p/7640937.html
Copyright © 2020-2023  润新知