[SinGuLaRiTy] 组合数学题目复习
【SinGuLaRiTy-1021】 Copyright (c) SinGuLaRiTy 2017. All Rights Reserved.
[CQBZOJ 2011] 计算系数
题目描述
给定一个多项式(ax + by)^k,请求出多项式展开后x^n y^m项的系数。
输入
共一行,包含 5 个整数,分别为a,b,k,n,m,每两个整数之间用一个空格隔开。
输出
输出共 1 行,包含一个整数,表示所求的系数,这个系数可能很大,输出对10007 取模后的结果。
样例数据
样例输入 | 样例输出 |
1 1 3 1 2
|
3
|
解析
这道题可以用递推解决:
设f[i][j]为(ax+by)^i的x^j*y^(n-j)的系数。显然可以得到公式: f[i][j]=(f[i-1][j-1]*a+f[i-1][j]*b)007。
时间复杂度O(N^2)
Code
#include<cstring> #include<cstdio> #include<cmath> #include<algorithm> #include<cstdlib> #define MOD 10007 using namespace std; int f[1005][1005]; int main() { int a,b,k,n,m; scanf("%d%d%d%d%d",&a,&b,&k,&n,&m); a%=MOD; b%=MOD; f[2][1]=a; f[1][2]=b; for(int i=1;i<=k;i++) { for(int j=1;j<=k;j++) { if(!(i==1&&j==2||i==2&&j==1)) f[i][j]=(f[i-1][j]*a+f[i][j-1]*b)%MOD; } } printf("%d",f[n+1][m+1]%MOD); return 0; }
[CQBZOJ 2713] 计算组合数
题目描述
给定正整数n, k,计算C(n, k)。答案保证在2^31以内。
输入
多组数据,每组数据仅一行,即2个整数n和k (n>=1) and k (0<=k<=n). 以2个0结束输入。
输出
对每个数据,输出对应的答案。
样例数据
样例输入 | 样例输出 |
4 2
10 5
49 6
0 0
|
6
252
13983816
|
解析
板题,无须解释。
Code
#include<iostream> #include<cstdio> #include<cstdlib> #include<algorithm> #include<cmath> #include<cstring> #define LL long long using namespace std; LL C(LL n ,LL k) { if(k>n/2) k=n-k; LL a=1; LL b=1; int i; for(i=1;i<=k;i++) { a*=n-i+1; b*=i; if(!(a%b)) { a/=b; b=1; } } return a/b; } int main() { LL n,k; for(;;) { scanf("%lld%lld",&n,&k); if(!n&&!k) break; printf("%lld\n",C(n,k)); } return 0; }
[CQBZOJ 1490] 组合数学一
题目描述
N个盒子排成一行(1<=N<=20)。你有A个红球和B个蓝球。0 <= A <= 15, 0 <= B <= 15。球除了颜色没有任何区别。你可以将球放进盒子。一个盒子可以同时放进两种球,也可以只放一种,也可以空着。球不必全部放入盒子中。编程计算有多少种放置球的方法。
输入
一行,N,A,B,用空格分开。
输出
一行,输出放置方案总数。
样例数据
样例输入 | 样例输出 |
2 1 1
|
9
|
解析
Code
#include<iostream> #include<cstdio> #include<cstring> #define ll long long unsigned using namespace std; ll c(ll n,ll r) { ll ans=1; ll i; for(i=1;i<=r;i++) { ans*=(n-i+1); ans/=i; } return ans; } int main() { ll k,a,b; cin>>k>>a>>b; cout<<c(a+k,a)*c(b+k,b); return 0; }
[CQBZOJ 1491] 组合数学二
题目描述
有n个正整数排成一行。你的目的是要从中取出一个或连续的若干个数,使它们的和能够被k整除。 例如,有6个正整数,它们依次为1、2、6、3、7、4。若k=3,则你可以取出1、2、6,或者2、6、3、7,也可以仅仅取出一个6或者3使你所取的数之和能被3整除。当然,满足要求的取法不止以上这4种。事实上,一共有7种取法满足要求。 给定n和k,以及这n个数。你的任务就是确定,从这n个数中取出其中一个数或者若干连续的数使它们的和能被k整除有多少方法。 由于取法可能很多,因此你只需要输出它mod 1234567的值即可。
输入
第一行有两个正整数,分别代表n和k。输入数据保证有n小于等于500 000,k小于等于100 000。 以下n行每行一个正整数。这些正整数保证都不大于10 000。
输出
一个正整数。它应该是你的答案mod 1234567的结果。
样例数据
样例输入 | 样例输出 |
6 3
1
2
6
3
7
4
|
7
|
解析
SUM[i]是代表前i个数的和
当(SUM[i]-SUM[j]) MOD k=0 这时[j+1,i]就是满足的一个区间 一个方案了
而我们求的是(SUM[i]-SUM[j]) MOD k=0 这样的方案总个数
我们又可以推出 上式等价于SUM[i] MOD k=SUM[j] MOD k
所以我们就是求SUM[i] MOD k=SUM[j] MOD k 的方案个数了
假设 sum[i],sum[j],..sum[k](共bn个) 都是 MOD k 余数为k-1的sum
那么从上面bn个sum中任意选取两个就能得出(SUM[i]-SUM[j]) MOD k=0
那么在bn个sum中怎么配对呢
(下面的sum[bn]表示上述bn个sum中的第n个sum)
很简单 先是sum[b1]与sum[b2] sum[b3] ...sum[bn] (bn-1 个)
然后sum[b2]与sum[b3] sum[b4] ...sum[bn] (bn-2 个)
然后sum[b3]与sum[b4] sum[b5] ...sum[bn] (bn-3 个)
............
最后sum[bn-1]与sum[bn] ( 1 个)
方案总数=n-1+n-2+n-3+...+1=bn*(bn-1) div 2
所以 当sum mod k的余数为k-1时有bn*(bn-1) div 2个方案总数了
就这样依次得出余数为k-1 k-2 k-3 ...0的时候方案总数 再相加一下得出答案
所以在读入一个数的时候就计算sum然后计算sum mod k 的余数
而b[j]表示余数为j的sum个数 此时根据上面新得出的更新相应的b[j]
这样在读入完毕之后就可以根据b[j]直接计算总方案数了
Code
#include<cstdio> #include<cstring> #define MAXN 500000 #define MOD 1234567 int mod_num[MAXN+40]; int main() { int n,k,s=0,i,x; scanf("%d%d",&n,&k); mod_num[0]++; for(i=1;i<=n;i++) { scanf("%d",&x); s=(s+x)%k; mod_num[s]++; } s=0; for(i=0;i<k;i++) s=(s+mod_num[i]*(mod_num[i]-1LL)/2)%MOD; printf("%d",s); return 0; }
[CQBZOJ 1492] 组合数学三
题目描述
八是个很有趣的数字啊。八=发,八八=爸爸,88=拜拜。当然最有趣的还是8用二进制表示是1000。怎么样,有趣吧。当然题目和这些都没有关系。 某个人很无聊,他想找出[a,b]中能被8整除却不能被其他一些数整除的数。
输入
第一行一个数n,代表不能被整除的数的个数。 第二行n个数,中间用空格隔开。 第三行两个数a,b,中间一个空格。 a < =b < =1000000000
输出
一个整数,为[a,b]间能被8整除却不能被那n个数整除的数的个数。
样例数据
样例输入 | 样例输出 |
3
7764 6082 462
2166 53442
|
6378
|
解析
Code
#include<iostream> #include<cstdio> #include<cmath> using namespace std; typedef unsigned long long ULL; int a[16],b[16]; int N; ULL A,B,ans; ULL gcd(ULL a,ULL b){return b?gcd(b,a%b):a;} void Dfs(int i,ULL num,int cnt){ if(i>N){ if(cnt&1) ans+=B/num-A/num; else ans-=B/num-A/num; return ; } Dfs(i+1,num/gcd(num,a[i])*a[i],cnt+1); Dfs(i+1,num,cnt); } int main(){ scanf("%d",&N); for(int i=1; i<=N; ++i) scanf("%d",&a[i]); scanf("%llu%llu",&A,&B); Dfs(1,8,1); printf("%llu\n",ans); return 0; }
[POJ 1173] 条形码
题目描述
条形码如下图所示,由黑白两色构成,每种颜色分布一定的宽度。宽度是一个有上限的整数值。例如下图的条形码有4个条形,宽度分别是1,2,3,4,总宽度为:1+2+3+1 =7
定义BC(n,k,m) 表示有k个条形,每个条形的宽度不超过m,总宽度为n的条形码的总个数。例如BC(7,4,3)表示了如下所有的条形码:
0: 1000100 | 8: 1100100
1: 1000110 | 9: 1100110
2: 1001000 | 10: 1101000
3: 1001100 | 11: 1101100
4: 1001110 | 12: 1101110
5: 1011000 | 13: 1110010
6: 1011100 | 14: 1110100
7: 1100010 | 15: 1110110
其中1表示黑色,0表示白色。冒号前的数字表示了它的字典序号。上图中的条形码的序号为4。
你的任务是读入n, k, m,计算满足要求的条形码的总个数,以及读入若干个条形码,计算对应的字典序号。
输入
第1行:3个整数n,k,m(1 <= n,k,m <= 33)
第2行:1个整数s(0 <= s <= 100).
接下来s行,每行1个01串,表示一个条形码
输出
第1行:1个整数,表示满足要求的条形码的总个数
接下来s行,每行1个整数,表示对应条形码的字典序
样例数据
样例输入 | 样例输出 |
7 4 3
5
1001110
1110110
1001100
1001110
1000100
|
16
4
15
3
4
0
|
解析
题目可以转化为:有n的长度,k个木板,每个木板的长度最多为m,问有多少种组合方式,对于给定的组合是字典序的第多少个。
于是有了思路:用dp[i][j]表示后i个木板组成长度为j的有多少种情况。确定给定的组合是第几个的话,就从前往后数,奇数的木板,假如给定的长度为3,那么就答案就加上当它为1和2的时候后面的情况数,偶数的是长度从大到小加上它后面的情况。
Code
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; typedef long long ll; ll dp[40][40]; int num[40]; char s[110]; int n,k,m; int main() { int i,j,a,b,p,cas,pos,len; ll ans,ret; while(~scanf("%d%d%d",&n,&k,&m)) { memset(dp,0,sizeof(dp)); dp[0][0]=1; for(i=1;i<=k;i++) for(j=1;j<=n;j++) { for(a=1;a<=m && j-a>=0;a++) dp[i][j]+=dp[i-1][j-a]; } ans=0; printf("%I64d\n",dp[k][n]); scanf("%d",&cas); while(cas--) { scanf("%s",s+1); pos=0; for(i=1;i<=k;i++) { pos++; num[i]=1; while(pos<n && s[pos]==s[pos+1]) { num[i]++; pos++; } } len=n; ans=0; for(i=k;i>=1;i--) { p=k-i+1; if(p&1) for(j=1;j<num[p] && len-j>0;j++) ans+=dp[i-1][len-j]; else for(j=min(m,len);j>num[p];j--) ans+=dp[i-1][len-j]; len-=num[p]; } printf("%I64d\n",ans); } } return 0; }
[HDU 4390] 表达式计数
题目描述
输入
输出
对于每组测试数据,输出有多少种数列满足情况,结果对1e9+7取余
样例数据
样例输入 | 样例输出 |
2
3 4
|
4
|
解析
首先是将所有 的b进行素因子分解,则a和b的因子是完全一致的。
剩下的便是将所有b的因子,分给a
我们考虑某个素因子pi,如果有ci个,便成了子问题将ci个相同的物品放入到n个不同的容器中,种数为多少
但是要求ai>1,也就是容器不能为空,这是个问题。
我们考虑的是什么的情况,然后减去假设有一个确定是空的情况,发现可以用容斥原理解决
我们假设f[i]表示有i个容器的结果,c(n,i)*f[i]
将m个物品放到到不同的n个容器中,结果为c(n+m-1,n-1)
Code
#include<cstdio> #include<iostream> #include<algorithm> using namespace std; typedef long long LL; const int maxn=1005; const int MOD=1000000007; int p[maxn]; int a[maxn]; LL c[maxn][maxn]; void init() { int i,j; for(i=0;i<maxn;i++) { c[i][i]=c[i][0]=1; for(j=1;j<i;j++) c[i][j]=(c[i-1][j]+c[i-1][j-1])%MOD; } } int getnum(int m,int n){ return c[m+n-1][n-1]; } LL solve(int cnt,int n){ int i,j; LL ans=1; for(i=0;i<=cnt;i++) ans=(ans*getnum(a[i],n))%MOD; for(i=1;i<n;i++) { LL tmp=c[n][i]; for(j=0;j<=cnt;j++) tmp=(tmp*getnum(a[j],n-i))%MOD; if(i&1) ans=((ans-tmp)%MOD+MOD)%MOD; else ans=(ans+tmp)%MOD; } return ans; } int main() { init(); int n; while(scanf("%d",&n)!=EOF) { int t,i,j; int cnt=0; for(i=0;i<n;i++) { scanf("%d",&t); for(j=2;j*j<=t;j++) { while(t%j==0){ p[cnt++]=j; t/=j; } } if(t>1) p[cnt++]=t; } sort(p,p+cnt); a[0]=1; i=0; for(j=1;j<cnt;j++) { if(p[j]==p[j-1]) a[i]++; else a[++i]=1; } cnt=i; printf("%lld\n",solve(cnt,n)); } return 0; }
[CQBZOJ 1986 & COCI11-12 #4 & BZOJ 3181] 纠结的数(BROJ)
题目描述
找出第N小的正整数X,满足条件X的最小的素因子是P。如果X的值超过10^9,则输出0。
输入
第1行:2个整数N和P (1 ≤ N, P ≤ 10^9),P一定是素数
输出
第1行:满足条件的数X或者0
样例数据
样例输入 | 样例输出 |
2 3
|
9
|
解析
实在是做不出来了。首先到官网上看了看题解:
To solve this for large values of P we will use modification of the sieve of Eratosthenes. Size of our sieve will be 109 / P. Integers in the sieve represent multiples of P. During the execution of this algorithm we can find smallest prime factors or mark only multiples of prime numbers smaller than P as in the official solution.
For smaller values of P we can binary search through [1, 109 / P], again looking at these numbers as the corresponding multiples of P. For some number we must find the number of integers not greater and relatively prime with that number. We can do this by using inclusionexclusion principle with prime numbers less than P.
With careful implementation this solution can work for much larger values of P than requested for this subtask.
We can also solve this task for smaller value of P by making use of periodic behaviour of smallest prime factors. Let A(n) be the smallest prime factor of n, B(k) the k-th prime number, and T(k) the product of first k primes. For A(n) ≤ B(k), A(n + T(k)) = A(n) holds. So it’s enough to know A(n) for n ≤ T(k) in order to find the N-th prime who’s smallest prime factor is B(k)
......看完想骂人,还是看中文的吧:
因为p是x的最小素因子,所以当p>sqrt(n),即p>sqrt(10^9)≈10^5时直接输0。
对于剩下的部分,先筛出小于p的素数。
当p>=29时,直接标记Max(10^9)/p以内小于p素数的倍数.
当p<29时,二分答案+容斥原理(能被p整除,不能被小于p的其它整素数整除)
(Tips: 不知道如何确定P的最佳值。在CQBZOJ上测试出: p若取到24以下,会RE;若取到52以上,会WA(有人说p=65时最优,(⊙_⊙?)). 具体情况视不同评测环境而定)
Code
#include<cstdio> #include<iostream> #include<cstdlib> #include<cmath> #include<algorithm> using namespace std; typedef long long LL; const int Max=1000000000; const int N=20000010; const int M=100010; int ans,n,p,cnt,LCM,num,prime[M]; bool vis[M],used[N]; int gcd(int a,int b) { int c; while(b) { c=b; b=a%b; a=c; } return a; } int lcm( int a, int b ) { return a/gcd(a,b)*b; } void dfs(int pos,int flag,int a,int b ) { if(pos>cnt) { if(flag&1) ans-=b/LCM-(a-1)/LCM; else ans+=b/LCM-(a-1)/LCM; return ; } int k=LCM; dfs(pos+1,flag,a,b); LCM=lcm(LCM,prime[pos]); dfs(pos+1,flag+1,a,b); LCM=k; } int devide(int l,int r) { if(l==r) return l; int mid=(l+r)>>1; LCM=p; ans=0; dfs(1,0,p+1,mid); if(ans>=n) return devide(l,mid); else return devide(mid+1,r); } int main() { scanf("%d%d",&n,&p); if(n==1) { printf("%d\n",p); return 0; } if(p*1LL*p*1LL>Max) { putchar(48); return 0; } for(int i=2;i<p;++i) { if(vis[i]) continue; prime[++cnt]=i; for(int j=i;j<p;j+=i) vis[j]=1; } if(p<29) { --n; int v=devide(p+1,Max); for(int i=1;i<=cnt;++i) if(v%prime[i]==0) { v=0; break ; } printf("%d\n",v); return 0; } int mm=Max/p; for(int i=1;i<=cnt;++i) for(int j=prime[i];j<=mm;j+=prime[i]) used[j]=1; int i; for(i=1;i<=mm;++i) { if(!used[i]) num++; if(num==n) break ; } if(num==n) printf("%d\n",p*i); else putchar(48); return 0; }
[CQBZOJ 2716] 无关的元素
题目描述
对于给定的n个数a1,a2,...,an,依次求出相邻两数之和,将得到一个新数列。重复上述操作,最后结果将变成一个数。问这个数除以m的余数与哪些数无关?
例如n=3,m=2时,第一次求和得到a1+a2,a2+a3,再次求和得到a1+2a2+a3,它除以2的余数和a2无关。
输入
第1行:2个整数n和m(1<=n<=10^5, 2 <=m<=10^9)
输出
按升序列出与m无关的元素的序号,每行1个。
若与全部元素无关,输出0
样例数据
样例输入 | 样例输出 |
5 3
|
3
|
解析
只要判断C(n-1,i-1)能否被 m整除即可。
做法是先分解m的质因数,然后计算1!~(n-1)! 包含m的质因数的个数
C(n-1,i-1) = (n-1)!/((i-1)!*(n-i)!)
只要判断 剩下的质因数的个数是否大于等于m的任一个质因数的个数即可
Code
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define MAXN 100000 using namespace std; int s[MAXN + 10][2]; int n,m; int cnt; int ans[20]; void dive(int x){ int rt = sqrt(x + 0.5); for(int i = 2;i <= rt;i++){ if(x % i == 0) s[++cnt][0] = i; while(x % i == 0){ s[cnt][1]++; x /= i; } } if(x > 1){ s[++cnt][0] = x; s[cnt][1] = 1; } } void dive(int x,int k){ for(int i = 1;i <= cnt;i++){ while(x % s[i][0] == 0){ ans[i] += k; x /= s[i][0]; } if(x == 1) break; } } bool check(){ for(int i = 1;i <= cnt;i++) if(ans[i] < s[i][1]) return false; return true; } int main(){ scanf("%d%d",&n,&m); n--; dive(m); bool flag = true; if(m == 1) { flag = false; printf("1\n"); } int ans = 1; for(int i = 1;i < n;i++){ dive(n - i + 1,1); ans *= (n - i + 1); dive(i,-1); ans /= i; if(check()){ printf("%d\n",i + 1); flag = false; } } if(flag) printf("0"); return 0; }
[HDU 4248] A Famous Stone Collector
题目描述
Mr. B loves to play with colorful stones. There are n colors of stones in his collection. Two stones with the same color are indistinguishable. Mr. B would like to
select some stones and arrange them in line to form a beautiful pattern. After several arrangements he finds it very hard for him to enumerate all the patterns. So he asks you to write a program to count the number of different possible patterns. Two patterns are considered different, if and only if they have different number of stones or have different colors on at least one position.
有N种石头,每种石头有A1,A2....AN个,现取出一些石头组成序列,求可以组成多少种序列。
输入
Each test case starts with a line containing an integer n indicating the kinds of stones Mr. B have. Following this is a line containing n integers – the number of
available stones of each color respectively. All the input numbers will be nonnegative and no more than 100.
输出
For each test case, display a single line containing the case number and the number of different patterns Mr. B can make with these stones, modulo 1,000,000,007,
which is a prime number.
样例数据
样例输入 | 样例输出 |
3 1 1 1 2 1 2 |
Case 1: 15 Case 2: 8 |
解析
我们采用动态规划的思想,划分阶段:按照石头种类划分阶段。于是乎,对于第i种石头,相当于之前石头的颜色并不重要,借助高中数学插板法的思想,假如之前的 i-1 种石头,拼出了长度为len,那么,相当于有len+1个空,咱们要放第 i 种石头进去,于是乎,转化成了经典问题。
于是设计状态:DP[i][j] 表示 用前 i 种石头,排出长度为 j 的可能数这里,第 i 种石头互相没有区别,len+1 个空有序,相当于有区别,可以有空盒,于是,如果从第 i 种中放put个进去,情况数应该是 C(put+len,put)
然后,状态转移的时候,枚举在阶段 i 放入 put 个,DP[i+1][j+put]+=DP[i][j] * C(put + j, put) 即可
复杂度 10000 ^ 2,能过……
Code
#include<cstdio> #include<queue> #include<cstring> #include<iostream> #include<algorithm> #define LL long long #define INF 0x3f3f3f3f #define N 10010 #define mod 1000000007 using namespace std; int num; LL dp[110][N]; LL C[N][110]; void init() { C[0][0]=1; for(int i=1;i<N;i++) for(int j=0;j<=100;j++) { if(!j) C[i][j]=C[i-1][j]; else C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod; } } int main() { init(); int cas=1; int n; while(~scanf("%d",&n)) { memset(dp,0,sizeof(dp)); dp[0][0]=1; int sum=0; for(int i=1;i<=n;i++) { scanf("%d",&num); sum+=num; for(int k=0;k<=num;k++) for(int j=k;j<=sum;j++) dp[i][j]=(dp[i][j]+(dp[i-1][j-k]*C[j][k]%mod))%mod; } LL ans=0; for(int i=1;i<=sum;i++) ans=(ans+dp[n][i])%mod; printf("Case %d: ",cas++); printf("%lld\n",ans); } return 0; }
[HDU 4372] Count The Buildings
题目描述
There are N buildings standing in a straight line in the City, numbered from 1 to N. The heights of all the buildings are distinct and between 1 and N. You can see F buildings when you standing in front of the first building and looking forward, and B buildings when you are behind the last building and looking backward. A building can be seen if the building is higher than any building between you and it.
Now, given N, F, B, your task is to figure out how many ways all the buildings can be.
不同高度的建筑物排成一列。总共有N栋建筑。从前看,可以看到F栋建筑;从后看,可以看到B栋建筑。问:这些建筑有多少种满足条件的排列方式。
输入
First line of the input is a single integer T (T<=100000), indicating there are T test cases followed.
Next T lines, each line consists of three integer N, F, B, (0<N, F, B<=2000) described above.
输出
For each case, you should output the number of ways mod 1000000007(1e9+7).
样例数据
样例输入 | 样例输出 |
2 3 2 2 3 2 1 |
2 1 |
解析
首先我们知道一个结论:n的环排列的个数与n-1个元素的排列的个数相等,因为P(n,n)/n=(n-1)!。
可以肯定,无论从最左边还是从最右边看,最高的那个楼一定是可以看到的.
假设最高的楼的位置固定,最高楼的编号为n,那么我们为了满足条件,可以在楼n的左边分x-1组,右边分y-1组,且用每组最高的那个元素代表这一组,那么楼n的左边,从左到右,组与组之间最高的元素一定是单调递增的,且每组中的最高元素一定排在该组的最左边,每组中的其它元素可以任意排列(相当于这个组中所有元素的环排列)。右边反之亦然。
然后,可以这样考虑这个问题,最高的那个楼左边一定有x-1个组,右边一定有y-1个组,且每组是一个环排列,这就引出了第一类Stirling数(n个人分成k组,每组内再按特定顺序围圈的分组方法的数目)。
我们可以先把n-1个元素分成x-1+y-1组,然后每组内部做环排列。再在所有组中选取x-1组放到楼n的左边。所以答案是 ans(n,f,b)=C[f+b-2][f-1]*S[n-1][f+b-2] 。
<忘记第一类Stirling数?>
第一类Stirling数s(p,k)表示将p个元素分解为k个非空循环排列的方案数。
◎它的递推公式: s(p,k)=(p-1)*s(p-1,k)+s(p-1)(k-1), (1≤k≤p-1)
◎推导:若p个元素构成k个圆排列,考虑第p个元素的情况:
1> 如果前p-1个元素构成了k-1个圆排列,则第p个元素单独构成一个环,方案数:
s(p-1)(k-1)
2> 如果前p-1个元素构成了k个圆排列,那么第p个元素可以插入到任意元素的左边,方案数:
(p-1)*s(p-1,k)
◎边界条件:s(p,0)=0,p≥1;s(p,p)=1,p≥0.
Code
#include<cstring> #include<cstdio> #include<cmath> using namespace std; typedef long long LL; const int N=2005; const LL MOD=1000000007; LL C[N][N]; LL S[N][N]; void Init() { for(int i=0;i<N;i++) { C[i][0]=1; C[i][i]=1; S[i][0]=0; S[i][i]=1; for(int j=1;j<i;j++) { C[i][j]=(C[i-1][j]%MOD+C[i-1][j-1]%MOD)%MOD; S[i][j]=((i-1)%MOD*S[i-1][j]%MOD+S[i-1][j-1]%MOD); } } } int main() { LL t,n,f,b,ans; Init(); scanf("%I64d",&t); while(t--) { scanf("%I64d%I64d%I64d",&n,&f,&b); if(f+b-2>n)//有一组数据需要特判,否则RE puts("0"); else { ans=C[f+b-2][f-1]%MOD*S[n-1][f+b-2]%MOD; printf("%I64d\n",ans); } } return 0; }
[HDU 3208] Integer's Power
题目描述
LMY and YY are number theory lovers. They like to find and solve some interesting number theory problems together. One day, they become interested in some special numbers, which can be expressed as powers of smaller numbers.
For example, 9=3^2, 64=2^6, 1000=10^3 …
For a given positive integer y, if we can find a largest integer k and a smallest positive integer x, such that x^k=y, then the power of y is regarded as k.
It is very easy to find the power of an integer. For example:
The power of 9 is 2.
The power of 64 is 6.
The power of 1000 is 3.
The power of 99 is 1.
The power of 1 does not exist.
But YY wants to calculate the sum of the power of the integers from a to b. It seems not easy. Can you help him?
定义数的Power:对于给定的正整数y,如果我们可以找到最大的整数k和最小的正整数x,使得x ^ k = y,则y的Power被认为是k。(1的Power不存在)
现在,要求求出从a到b的每一个数的Power的和。
输入
The input consists of multiple test cases.
For each test case, there is one line containing two integers a and b. (2<=a<=b<=10^18)
End of input is indicated by a line containing two zeros.
输出
For each test case, output the sum of the power of the integers from a to b.
样例数据
样例输入 | 样例输出 |
2 10 248832 248832 0 0 |
13 5 |
解析
对于一个数n,从1~n中假设有x个数是满足p^k形式的,这里的k最多到62,那么对于每一个k,我们需要找到这个数x满足x^k最接近n,那么现在的问题就是对于每一个k找出对应的x,那么这个怎么找呢?
我们可以这样考虑,由于是找最接近的,我们可以先大致确定一个数r,那么可以通过r=pow(n,1/k)来计算,然后我们分别计算(r-1)^k,r^k,(r+1)^k,然后看这三个数哪个最接近n就行了,这里要注意(r+1)^k计算时可能会超过LL,所以有一些处理。然后就是相当于容斥的部分了。
Code
#include<iostream> #include<cstring> #include<cstdio> #include<cmath> using namespace std; typedef long long LL; const LL INF=1e18+300; const LL T=(LL)1<<31; LL num[105]; LL multi(LL a,LL b) { LL ans=1; while(b) { if(b&1) { double judge=1.0*INF/ans; if(a>judge) return -1; ans*=a; } b>>=1; if(a>T&&b>0) return -1; a=a*a; } return ans; } LL find(LL x,LL k) { LL r=(LL)pow(x,1.0/k); LL t,p; p=multi(r,k); if(p==x) return r; if(p>x||p==-1) r--; else { t=multi(r+1,k); if(t!=-1&&t<=x) r++; } return r; } LL Solve(LL n) { int i,k=0; memset(num,0,sizeof(num)); if(n<=3) return n; num[1]=n; for(i=2;i<63;i++) { num[i]=find(n,i)-1; if(!num[i]) break; } k=i; for(int i=k-1;i>0;i--) for(int j=1;j<i;j++) if(i%j==0) num[j]-=num[i]; LL ans=num[1]; for(int i=2;i<k;i++) ans+=(i*num[i]); return ans; } int main() { LL n,m; while(cin>>m>>n) { if(m==0&&n==0) break; cout<<Solve(n)-Solve(m-1)<<endl; } return 0; }
[HDU 3944] DP?
题目描述
![](http://acm.hdu.edu.cn/data/images/C363-1005-1.jpg)
Figure 1 shows the Yang Hui Triangle. We number the row from top to bottom 0,1,2,…and the column from left to right 0,1,2,….If using C(n,k) represents the number of row n, column k. The Yang Hui Triangle has a regular pattern as follows.
C(n,0)=C(n,n)=1 (n ≥ 0)
C(n,k)=C(n-1,k-1)+C(n-1,k) (0<k<n)
Write a program that calculates the minimum sum of numbers passed on a route that starts at the top and ends at row n, column k. Each step can go either straight down or diagonally down to the right like figure 2.
As the answer may be very large, you only need to output the answer mod p which is a prime.
在如图一分布的杨辉三角中,设计一条从最顶端到第n行第k列的路线,使其满足以下要求:1>每一步都向下或向右倾斜;2>经过的数字和最小。求出最小和模一个素数P的结果。
输入
Input to the problem will consists of series of up to 100000 data sets. For each data there is a line contains three integers n, k(0<=k<=n<10^9) p(p<10^4 and p is a prime) . Input is terminated by end-of-file.
输出
For every test case, you should output "Case #C: " first, where C indicates the case number and starts at 1.Then output the minimum sum mod p.
样例数据
样例输入 | 样例输出 |
1 1 2 4 2 7 |
Case #1: 0
Case #2: 5
|
解析
<Lucas定理+预处理>
显然第 n 行第 k 列的数就是组合数 C(n,k) ,答案满足对称性。只需要讨论 k <=n / 2 的情况。
考虑从目的地往上走到顶点,因为组合数在 k <= n / 2 是递增的,所以每次只要斜向上走,到了最左端,再往上走就可以得到最小的和,所以最小的和为
C(n,k)+C(n-1,k-1)+C(n-2,k-2)+......+C(n-k,0)+n-k;
下面就是求这个数的问题。
用 C(n-k+1,0)替换掉 C(n-k,0)后,得到:
C(n-k+1,0)+C(n-k+1,1)+C(n-k+2,2)+......+C(n-1,k-1)+C(n,k)+n-k
对于组合数有 C(n,k)=C(n-1,k-1)+C(n-1,k)
所以最左边两个数相加得 C(n-k+2,1),继续与 C(n-k+2,2)相加得到 C(n-k+3,2),一直加下去最后得到 C(n+1,k)。
所以最小的和为 C(n+1,k)+n-k。
Code
#include<iostream> #include<cstdio> #include<cstring> #define N 10005 using namespace std; bool prime[N]; int p[N],f[N][N],inv[N][N],cnt,pth[N]; void isprime() { cnt=0; memset(prime,1,sizeof(prime)); for(int i=2;i<N;i++) { if(prime[i]) { p[++cnt]=i; pth[i]=cnt; for(int j=i+i;j<N;j+=i) prime[j]=0; } } } int ksm(int a,int b,int m) { int ans=1; a%=m; while(b) { if(b&1) { ans=ans*a%m; b--; } b>>=1; a=a*a%m; } return ans; } void init() { for(int i=1;i<=cnt;i++) { f[i][0]=inv[i][0]=1; for(int j=1;j<p[i];j++) { f[i][j]=(f[i][j-1]*j)%p[i]; inv[i][j]=ksm(f[i][j],p[i]-2,p[i]); } } } int com(int n,int m,int P) { if(m>n) return 0; if(m==n) return 1; int t=pth[P]; return f[t][n]*(inv[t][n-m]*inv[t][m]%P)%P; } int lucas(int n,int m,int P) { if(m==0) return 1; return com(n%P,m%P,P)*lucas(n/P,m/P,P)%P; } int main() { int cas=1,n,m,P; isprime(); init(); while(scanf("%d%d%d",&n,&m,&P)!=EOF) { if(m<=n/2) m=n-m; n++; printf("Case #%d: %d\n",cas++,(m%P+lucas(n,m+1,P))%P); } return 0; }
[HDU 1695] GCD
题目描述
Given 5 integers: a, b, c, d, k, you're to find x in a...b, y in c...d that GCD(x, y) = k. GCD(x, y) means the greatest common divisor of x and y. Since the number of choices may be very large, you're only required to output the total number of different number pairs.
Please notice that, (x=5, y=7) and (x=7, y=5) are considered to be the same.
<You can assume that a = c = 1 in all test cases.>
给定a,b,c,d,k,要求在区间[a,b]内找到x,在[c,d]内找到y,使得gcd(x,y)=k。输出所有满足条件的条件数。
输入
The input consists of several test cases. The first line of the input is the number of the cases. There are no more than 3,000 cases.
Each case contains five integers: a, b, c, d, k, 0 < a <= b <= 100,000, 0 < c <= d <= 100,000, 0 <= k <= 100,000, as described above.
输出
For each test case, print the number of choices. Use the format in the example.
样例数据
样例输入 | 样例输出 |
2 1 3 1 5 1 1 11014 1 14409 9 |
Case 1: 9 Case 2: 736427 |
解析
只需要枚举x,然后确定另一个区间里面有多少个y就可以了。因此问题转化成为区间(1, d)里面与x互素的数的个数
先求出x的所有质因数,因此(1,d)区间里面是x的质因数倍数的数都不会与x互素,因此,只需要求出这些数的个数,减掉就可以了。
如果w是x的素因子,则(1,d)中是w倍数的数共有d/w个。
Code
#include<cstdio> #include<cstring> #include<vector> #include<algorithm> #define N 100005 #define ll long long using namespace std; vector<int> x[N]; bool is[N]; void prime() { memset(is,false,sizeof(is)); for(int i=0;i<N;i++) x[i].clear(); for(int j=2;j<N;j+=2) x[j].push_back(2); for(int i=3;i<N;i+=2) if(!is[i]) for(int j=i;j<N;j+=i) { is[j]=true; x[j].push_back(i); } } int work(int u,int s,int w) { int cnt=0,v=1; for(int i=0;i<x[w].size();i++) { if((1<<i)&s) { cnt++; v*=x[w][i]; } } int all=u/v; if(cnt%2==0) return -all; else return all; } int main() { prime(); int T,a,b,c,d,k; scanf("%d",&T); for(int cas=1;cas<=T;cas++) { scanf("%d%d%d%d%d",&a,&b,&c,&d,&k); if(k==0) { printf("Case %d: 0\n",cas); continue; } b/=k; d/=k; if (b>d) { a=b; b=d; d=a; } long long ans=0; for(int i=1;i<=d;i++) { k=min(i,b); ans+=k; for(int j=1;j<(1<<x[i].size());j++) ans-=work(k,j,i); } printf("Case %d: %I64d\n",cas,ans); } return 0; }
[HDU 1124] Factorial
题目描述
The most important part of a GSM network is so called Base Transceiver Station (BTS). These transceivers form the areas called cells (this term gave the name to the cellular phone) and every phone connects to the BTS with the strongest signal (in a little simplified view). Of course, BTSes need some attention and technicians need to check their function periodically.
ACM technicians faced a very interesting problem recently. Given a set of BTSes to visit, they needed to find the shortest path to visit all of the given points and return back to the central company building. Programmers have spent several months studying this problem but with no results. They were unable to find the solution fast enough. After a long time, one of the programmers found this problem in a conference article. Unfortunately, he found that the problem is so called "Travelling Salesman Problem" and it is very hard to solve. If we have N BTSes to be visited, we can visit them in any order, giving us N! possibilities to examine. The function expressing that number is called factorial and can be computed as a product 1.2.3.4....N. The number is very high even for a relatively small N.
The programmers understood they had no chance to solve the problem. But because they have already received the research grant from the government, they needed to continue with their studies and produce at least some results. So they started to study behaviour of the factorial function.
For example, they defined the function Z. For any positive integer N, Z(N) is the number of zeros at the end of the decimal form of number N!. They noticed that this function never decreases. If we have two numbers N1<N2, then Z(N1) <= Z(N2). It is because we can never "lose" any trailing zero by multiplying by any positive number. We can only get new and new zeros. The function Z is very interesting, so we need a computer program that can determine its value efficiently.
求N!末尾“0”的个数。
输入
There is a single positive integer T on the first line of input. It stands for the number of numbers to follow. Then there is T lines, each containing exactly one positive integer number N, 1 <= N <= 1000000000.
输出
For every number N, output a single line containing the single non-negative integer Z(N).
样例数据
样例输入 | 样例输出 |
6 3 60 100 1024 23456 8735373 |
0 14 24 253 5861 2183837 |
解析
首先我们要知道,已经在末尾产生的"0"是不会在后续的运算中消失的。
于是我们可以分析:在哪些情况下能在末尾产生"0"? 我们可以发现,这就需要因数可以分解得到2和5。
显然在N!中2的个数大于5的个数,所以只需求出5的个数即可
求 N! (1*2*3*4*5*...*N)里有多少个5其实可以转化成:
N!中:是5的倍数的数+是5^2的倍数的数+5^3.....
Code
#include<cstdio> #include<cmath> #include<algorithm> using namespace std; int main() { int T; int n; scanf("%d",&T); while(T--) { scanf("%d",&n); int cnt=0; int temp; temp=n/5; while(temp>0) { cnt+=temp; temp/=5; } printf("%d\n",cnt); } return 0; }
[HDU 4045] Machine scheduling
题目描述
A Baidu’s engineer needs to analyze and process large amount of data on machines every day. The machines are labeled from 1 to n. On each day, the engineer chooses r machines to process data. He allocates the r machines to no more than m groups ,and if the difference of 2 machines' labels are less than k,they can not work in the same day. Otherwise the two machines will not work properly. That is to say, the machines labeled with 1 and k+1 can work in the same day while those labeled with 1 and k should not work in the same day. Due to some unknown reasons, the engineer should not choose the allocation scheme the same as that on some previous day. otherwise all the machines need to be initialized again. As you know, the initialization will take a long time and a lot of efforts. Can you tell the engineer the maximum days that he can use these machines continuously without re-initialization.
有n台机器,从1~n标号。每一天,选出r台机器,并将其分为m组。在同一组内的机器需满足:任意两台机器的序号差需不小于k。问:有多少种不同的分配机器的方法。
输入
Input end with EOF.
Input will be four integers n,r,k,m.We assume that they are all between 1 and 1000.
输出
Output the maxmium days modulo 1000000007.
样例数据
样例输入 | 样例输出 |
5 2 3 2 | 6 |
解析
问题由两部分构成:第一,从N个机器中选出R个满足条件的机器的方案数;第二,将R个机器最多分为M组有的方案数。二者乘积即为答案。
第一部分:
先满足每两个机器之间至少有K-1个间隔,也就是还剩下rem=n-((r-1)*k+1)个机器可以随意安排,把这些多余的插入到R个机器之间(加上两端共R+1个位置)。问题也就变为rem个相同的球分到R+1个不同的组可以为空这种模型,不难推出是C(rem+R,R),可直接用插板法公式。
第二部分:
R个元素分为i个非空集合是第二类斯特林数,对i为1至m求和即可。
Code
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<iostream> #define min(a,b) ((a)<(b)?(a):(b)) #define ll long long const int mod=1000000007; const int N=1005; int C[2*N][2*N]; ll stir2[N][N]; void Init() { for(int i=1;i<=2000;i++) { C[i][0]=C[i][i]=1; for (j=1;j<i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod; } for(int i=1;i<=1000;i++) { stir2[i][0]=0; stir2[i][i]=1; for(int j=1;j<i;j++) stir2[i][j]=(stir2[i-1][j-1]+j*stir2[i-1][j])%mod; } } int main() { Init(); int n,m,k,r; while(~scanf("%d%d%d%d",&n,&r,&k,&m)) { int rem=n-((r-1)*k+1); if(rem<0) { printf("0\n"); continue; } ll sum=0,ans=C[rem+r][r]; for (int i=1;i<=m;i++) sum=(sum+stir2[r][i])%mod; ans=(ans*sum)%mod; printf("%I64d\n",ans); } return 0; }
[HDU 4532] 湫秋系列故事――安排座位
题目描述
为了给腾讯公司找到更多优秀的人才,HR湫秋最近去某高校组织了一次针对该校所有系的聚会,邀请了每个系的一些优秀学生来参加。
作为组织者,湫秋要安排他们的座位。这并不是一件很简单的事情,因为只有一排位置,并且位置总数恰好等于参加聚会的人数。为了促进交流,两个来自相同系的同学不可以座位相邻。湫秋现在希望知道有多少种不同的合理安排座位的方法(任意两个合理的安排方法,只要有一个位置的同学不同,都被认为是不同的)。
输入
输入第一行为T,表示有T组测试数据。
每组数据一个N开始,表示一共有多少个系。下面的一行包含N个整数Ai,表示每个系的到场人数。
[Technical Specification]
1. 1 <= T <= 47
2. 1 <= N, Ai <= 47
3. 1 <= Sum(Ai) <= 447
输出
对每组数据,先输出为第几组数据,然后输出结果。由于结果可能很大,输出对1,000,000,007 取余后的结果。
样例数据
样例输入 | 样例输出 |
3 2 1 2 2 1 3 3 1 2 3 |
Case 1: 2 Case 2: 0 Case 3: 120 |
解析
由于每个班级的人是不同的,我们先将所有人看作相同的,最后乘以一个全排列即可。用f[i][j]表示前i个班级的人排好后有j个位置两侧是同一个班级的人的排列方案数,然后枚举第i个分成几块k 有l块塞入j个不合法空隙中进行转移。(具体见代码)
Code
#include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #define ll long long #define m 1000000007 using namespace std; ll dp[50][480]; ll C[500][500]; ll A[500]; int a[50]; int main() { int t,n; C[0][0]=1; for(int i=1;i<480;i++) { C[i][0]=1; for(int j=1;j<i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%m; C[i][i]=1; } A[0]=A[1]=1; for(int i=2;i<480;i++) A[i]=(A[i-1]*i)%m; scanf("%d",&t); for(int ca=1;ca<=t;ca++) { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); memset(dp,0,sizeof(dp)); dp[1][a[1]-1]=1; ll sum=a[1]; //每次枚举之前一共有j个空位置 //将当前的d[i]分成k组:C[a[i]-1][k-1] //在j个空位置中插入k组中的u组:C[j][u] //还剩下k-u组,插入到sum+1-j个空位置 //注意:j表示一共有j个位置两侧是同一个班级的人 //一共有sum个人,那么还有sum+1-j位置两侧是不同班级的人 for (int i=2;i<=n;i++) { for(int j=0;j<sum;j++) for(int k=1;k<=a[i];k++) for(int u=0;u<=j&&u<=k;u++) dp[i][j-u+a[i]-1-(k-1)]=(dp[i][j-u+a[i]-k]+(((dp[i-1][j]*C[j][u])%m*C[sum+1-j][k-u])%m*C[a[i]-1][k-1])%m)%m; sum+=a[i]; } printf("Case %d: ",ca); ll ans=dp[n][0]; for(int i=1;i<=n;i++) ans=(ans*A[a[i]])%m; printf("%lld\n",ans); } return 0; }
[HDU 4810] Wall Painting
题目描述
Ms.Fang loves painting very much. She paints GFW(Great Funny Wall) every day. Every day before painting, she produces a wonderful color of pigments by mixing water and some bags of pigments. On the K-th day, she will select K specific bags of pigments and mix them to get a color of pigments which she will use that day. When she mixes a bag of pigments with color A and a bag of pigments with color B, she will get pigments with color A xor B.
When she mixes two bags of pigments with the same color, she will get color zero for some strange reasons. Now, her husband Mr.Fang has no idea about which K bags of pigments Ms.Fang will select on the K-th day. He wonders the sum of the colors Ms.Fang will get with different plans.
For example, assume n = 3, K = 2 and three bags of pigments with color 2, 1, 2. She can get color 3, 3, 0 with 3 different plans. In this instance, the answer Mr.Fang wants to get on the second day is 3 + 3 + 0 = 6.
Mr.Fang is so busy that he doesn’t want to spend too much time on it. Can you help him?
You should tell Mr.Fang the answer from the first day to the n-th day.
求n个数里面,取i个数异或的所有组合的和,i取1~n。
输入
There are several test cases, please process till EOF.
For each test case, the first line contains a single integer N(1 <= N <= 103).The second line contains N integers. The i-th integer represents the color of the pigments in the i-th bag.
输出
For each test case, output N integers in a line representing the answers(mod 106 +3) from the first day to the n-th day.
样例数据
样例输入 | 样例输出 |
4 1 2 10 1 |
14 36 30 8 |
解析
将n个数拆成30位2进制,由于每个二进制位异或后相加和原来的数异或相加是一样的,所以只需要对每一位累加计算,用组合数学取数就行了,奇数个异或得1,偶数个异或得0,再乘以自己的二进制位值,复杂度O(30*n*n)
Code
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<cstdlib> #include<algorithm> #define LL long long #define INF 0x3f3f3f3f using namespace std; const int maxn=1000+10; const int mod=1000000+3; LL c[maxn][maxn],a[35],p[35]; void init() { memset(c,0,sizeof(c)); for(int i=0;i<maxn-5;i++) c[i][i]=c[i][0]=1; for(int i=1;i<maxn-5;i++) for(int j=1;j<i;j++) c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod; p[0]=1; for(int i=1;i<32;i++) p[i]=(2*p[i-1])%mod; } void cal(int x) { int cnt=0; while(x) { if(x%2) a[cnt]++; x/=2; cnt++; } } int main() { int n,x; LL tmp,ans; init(); while(~scanf("%d",&n)) { memset(a,0,sizeof(a)); for(int i=0;i<n;i++) { scanf("%d",&x); cal(x); } for(int i=1;i<=n;i++) { ans=0; for(int j=0;j<32;j++) for(int k=1;k<=i;k+=2) { tmp=((LL)(p[j]*c[a[j]][k]*c[n-a[j]][i-k]))%mod; ans+=tmp; ans%=mod; } if(i==n) printf("%I64d\n",ans); else printf("%I64d ",ans); } } return 0; }
Time: 2017-07-08