【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; }
第二题
给定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; }
第三题
给定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; }