【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

 

posted @ 2017-10-09 14:26  ONION_CYC  阅读(399)  评论(0编辑  收藏  举报