数学,位运算,典型题
数学,自然想到组合数,逆元,阶乘
先来一发组合数相关,
#define MAXN 200001 const int mod=1000000007; typedef long long ll; int n,m,r,c; ll ans,s; ll inv[MAXN],fac[MAXN],dev[MAXN]; //inv[]逆元,fac[]阶乘,dev[]阶乘的逆元 void chuli(int x) { inv[1]=1; fac[1]=1; dev[1]=1; fac[0]=1; dev[0]=1; for (int i=2;i<=x;i++) { fac[i]=fac[i-1]*i%mod; inv[i]=inv[mod%i]*(mod-mod/i)%mod; dev[i]=dev[i-1]*inv[i]%mod; } } ll C(int n,int m) { // C (m) // (n) ll x=fac[n]*dev[n-m]%mod*dev[m]%mod; return x; }
这是最普通的了,
然后是一些典型题,
No.1
∑i=1n ∑j=i n Ai and Ai+1~~Aj
这道题要逐个位数去统计贡献
#include <iostream> #include <cstdio> #define ll long long using namespace std; int W,n,line[100002]; ll ans; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&line[i]); while(line[i]>>W)W++; } for(int w=0;w<W;w++) for(int i=1,j=1;i<=n;i=++j) if((line[i]>>w)&1){ while(j<=n&&(line[j]>>w)&1)j++; ans+=((ll)j-(ll)i)*(j-i+1)/2*(1<<w); } printf("%lld",ans); return 0; }
然后还有对xor的优化
1 ll num_xor(ll x) { 2 int modans=x%4; 3 if (modans==0) { 4 return x; 5 } 6 if (modans==1) { 7 return 1; 8 } 9 if (modans==2) { 10 return x+1; 11 } 12 return 0; 13 } 14 15 ll l,r; 16 scanf("%lld%lld",&l,&r); 17 ans=num_xor(r)^num_xor(l-1); 18 printf("%lld\n",ans);
这个只是 l xor l+1 xor l+2 ~~ xor r
连续xor的结果
还有错排公式
来吧
错排问题,是组合数学中的问题之一。考虑一个有n个元素的排列,若一个排列中所有的元素都不在自己原来的位置上,那么这样的排列就称为原排列的一个错排。 n个元素的错排数记为D(n)。研究一个排列错排个数的问题,叫做错排问题或称为更列问题。
D(n)=(n-1)[D(n-1)+D(n-2)]; D(1)=0; D(2)=1。
错排也有例题
比如这个
求有多少种长度为 n 的序列 A,满足以下条件:
1 ~ n 这 n 个数在序列中各出现了一次。
若第 i 个数 A[i] 的值为 i,则称 i 是稳定的。序列恰好有 m 个数是稳定的。
满足条件的序列可能很多,序列数对 109+7 取模。
#include <cstdio> #include <iostream> #include <string> #include <bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e6; const ll mod=1e9+7; ll n,m,ans; ll f[N+5]; ll mul[N+5]; int T; void chuli() { //错排预处理 f[0]=1; f[1]=0; for (int i=2;i<=N;i++) { f[i]=((i-1)*(f[i-1]+f[i-2])%mod)%mod; } //阶乘预处理 mul[0]=1; for (int i=1;i<=N;i++) { mul[i]=mul[i-1]*i%mod; } } //30~~43 求逆元 ll fast_pow(ll a,ll p) { ll ans=1; for (;p;p>>=1,a=a*a%mod) { if (p&1) { ans=ans*a%mod; } } return ans; } ll inv(ll x) { return fast_pow(x,mod-2); } ll get_C(ll n,ll m) { ll x=mul[n]%mod; ll y=mul[m]*mul[n-m]%mod; ll ans=x*inv(y)%mod; return ans; } int main () { chuli(); scanf("%d",&T); while (T--) { scanf("%lld%lld",&n,&m); ans=get_C(n,m)*f[n-m]%mod; printf("%lld\n",ans); } return 0; }
位运算还有典型题
No.2
所以对于一个序列,求出它们所有的连续和来说,小明觉得十分的简单。但今天小明遇到了一个序列和的难题,这个题目不仅要求你快速的求出所有的连续和,还要快速的求出这些连续和的异或值。小明很快的就求出了所有的连续和,但是小明要考考你,在不告诉连续和的情况下,让你快速求是序列所有连续和的异或值。
解释:
一般这种异或都是按位一位一位做的
对于某第k位,如果为1,那么说明
所有连续和异或的这第k位为1
如果满足这第k位为1的(s[i]-s[j])有cnt个
如果cnt为奇数,那么说明答案的第k位也等于1
如何求出第k位为1的(i,j)对数?
如果sum[i]第k位为1:
为了使第k位为1,要么sum[j]第k位为0且sum[j]前k-1位小于sum[i]前k-1位的大小
原因是如果红色条件不成立,进位后就变成了0
还有就是sum[j]第k位为1且sum[j]前k-1位大于sum[i]前k-1位的大小
同理,也是进位的问题
那么红色部分要求满足大小关系的对数,用两个树状数组就行
第k位为0同理
还可以这么解释:
一般这种位运算的题都要把每一位拆开来看,因为位运算每个位的结果这和这一位的数有关。
这样我们用s[i]表示a的前缀和,即a[1]+a[2]+....a[i],然后我们从这些数二进制最右位(2^0)开始,按照每一位对答案的贡献来计算。
假设我们现在算到最右位(2^0),并且位于第i个数,我们想要知道以i结尾的连续和对答案的贡献,只需要知道有多少s[i]-s[j](0<=j<i)的2^0位是1。 (设s[0]=0)
如果这个数是奇数,就说明异或了1奇数次,也就相当于异或了1,我们只需要把记录这一位总的异或贡献的变量cnt异或1即可;
如果是偶数就不用管了,对答案没有贡献。
对于数的每一位如果最后cnt=1的话,就说明在这一位所有连续和的异或和为1,我们就需要把答案加上(1<<(这个位数))。
那如何快速计算有多少个s[i]-s[j]的二进制第k位是否为1呢??
答案是利用权值树状数组。
考虑到Σa 最大才有1000000,我们构造两棵权值树状数组,一棵记录当前位为1的,另一棵记录为0的。
如果当前扫描到的s[i]的二进制第k位为1,那么对这一位的答案有贡献的只有那些第k位为1且第k位向右的数比s[i]第k位向右的数大的或者第k位为0且第k位向右的数不比s[i]第k位向右的数大的。(可能有点拗口,,都怪我语文学的不好)
为什么呢?
因为如果第k位都为1的话,那么只有后面那些位的和大于s[i]的数,s[i]减去它之后第k位才能出现1(因为s[i]比它小的话需要向更高位借数,就和小学学的横式减法差不多),从而对答案作出贡献;
如果第k位为0的话,如果后面再比s[i]大的话,s[i]第k位的1就需要借给低一位的了,所以后面必须不比s[i]大。
就是这样
1 #include <cstdio> 2 #include <iostream> 3 #include <string> 4 #include <bits/stdc++.h> 5 6 using namespace std; 7 #define MAXN 1000001 8 int c[MAXN][2]; 9 int s[MAXN],a[MAXN],ans; 10 int pw[21],n; 11 12 void add(int x,int y) { 13 while (x<=1000000) { 14 c[x][y]++; 15 x+=(x&(-x)); 16 } 17 } 18 int query(int x,int y) { 19 int sum=0; 20 while (x) { 21 sum+=c[x][y]; 22 x-=(x&(-x)); 23 } 24 return sum; 25 } 26 int main() { 27 int flag,cnt; 28 cin>>n; 29 for (int i=1; i<=n; i++) { 30 scanf("%d",&s[i]); 31 s[i]+=s[i-1]; 32 } 33 pw[0]=1; 34 for (int i=1; i<=20; i++) 35 pw[i]=pw[i-1]*2; 36 for (int i=0; i<=20; i++) { 37 if (pw[i]<=s[n]) { 38 memset(c,0,sizeof(c)); 39 flag=0; 40 add(1,0); 41 for (int j=1; j<=n; j++) { 42 int tmp=s[j]&pw[i]; 43 if (tmp) { 44 cnt=query(a[j]+1,0)+query(1000001,1)-query(a[j]+1,1); 45 } 46 else { 47 cnt=query(a[j]+1,1)+query(1000001,0)-query(a[j]+1,0); 48 } 49 if (cnt%2==1) flag^=1; 50 add(a[j]+1,(bool)tmp); 51 if (tmp) { 52 a[j]|=pw[i]; 53 } 54 } 55 if (flag) { 56 ans|=(pw[i]); 57 } 58 } 59 } 60 cout<<ans; 61 return 0; 62 }
啦啦
最后还要有小球与盒子的问题
于是就来了
情况1:
1、球同,盒同,盒不可以为空 Pm(N)--这符号表示部分数为m的N-分拆的个数,m是P的下标,为了好看我将大写的M弄成小写
2、球同,盒同,盒可以为空 Pm(N+M)--为什么要加M,与4为什么要在3的基础上加M是一样的,就是为了保证不为空
3、球同,盒不同,盒不可以为空 C(N-1, M-1)
4、球同,盒不同,盒可以为空 C(N+M-1, M-1)
5、球不同,盒同,盒不可以为空 S(N, M) --第二类斯特林数
6、球不同,盒同,盒可以为空 S (N, 1) + S(N, 2) + S(N, 3)
+ ... + S(N, M)
7、球不同,盒不同,盒不可以为空 M!
* S(N, M)
8、球不同,盒不同,盒可以为空 M^N --表示M的N次方
这里就是 S 求法
1 #include <cstdio> 2 #include <iostream> 3 #include <string> 4 #include <bits/stdc++.h> 5 6 using namespace std; 7 typedef long long ll; 8 typedef unsigned long long ull; 9 10 #define MAXN 10001 11 ll f[MAXN][MAXN]; 12 ll n,mod; 13 14 void dpit() { 15 f[1][1]=1; 16 for (int i=1;i<=n;i++) { 17 f[i][0]=0; 18 f[i][1]=1; 19 f[i][i]=1; 20 } 21 for (int i=2;i<=n;i++) { 22 for (int j=2;j<=n;j++) { 23 f[i][j]=(j*f[i-1][j])+f[i-1][j-1]; 24 f[i][j]%=mod; 25 } 26 } 27 } 28 29 30 int main () { 31 cin>>n>>mod; 32 dpit(); 33 for (int i=1;i<=n;i++) { 34 for (int j=1;j<=i;j++) { 35 cout<<f[i][j]<<" "; 36 } 37 cout<<endl; 38 } 39 40 return 0; 41 }