@CQUPTacm
2017-03-26T05:53:29.000000Z
字数 9725
阅读 1400
题解
A Eddy's AC难题 (HDU - 2200)
题意:
中文题。
题解:
符号说明,以C(a,b)表示从a个东西中无顺序的选择b个的方法数,以A(a,b)表示从a个东西中有顺序的选择b个的方法数,以(a<=i<=b)Σ[y]表示y从i=a到i=b的求和。
有很多种思路:
<1>从这n个人中选择x(2<=x<=n)个人,然后把这x个人分成AC数少和AC数多的两部分,有x-1种分法,答案就是(2<=x<=n)Σ[C(n,x)]
<2>枚举AC数少的那个部分AC数最多的人,那么比他少的所有人可选可不选,比他多的人至少选一个,答案就是(1<=x<n)Σ[2^(x-1)*(2^(n-x)-1)]
而组合数也有多种方法来求:
(1)通过定义公式C(a,b)=a!/(b!*(a-b)!),这种方法不常用,因为阶乘是非常大的,这道题就不能用这个办法。。。
(2)由上面的公式可以看出C(a,b-1)=a!/((b-1)!*(a-b+1)!),所以C(a,b)=C(a,b-1)*(a-b+1)/b,这种方法避免了阶乘过大的问题,对于这道题而言是可行的,不过对于有取模的情况也是比较尴尬的
(3)从a个东西中选择b个,相当于从前a-1个直接选b个然后不选第a个,或者从前a-1个中选b-1个,再选第a个,所以C(a,b)=C(a-1,b)+C(a-1,b-1),于是我们就可以通过打组合数表来求组合数,而且便于取模,唯一的问题就是当a、b很大的时候,时间复杂度是平方级的。。。ps:注意C(x,0)不论x取什么值,永远是1
(4)在有取模,而且模数是大于a和b的质数的时候,通过求逆元,可以用定义公式求
(5)还有一个神奇的东西叫Lucas
(4)、(5)提到的两种方法,有兴趣的同学可以自行去了解。
这里给出分别用(2)、(3)两种求组合数方法的方法<1>,还有方法<2>。
(2)求组合数,方法<1>时间复杂度o(n^2),空间复杂度o(1)。
(3)求组合数,方法<1>时间复杂度o(n^2),空间复杂度o(n^2)。
方法<2>时间复杂度o(n),空间复杂度o(n)。
代码:
(2)求组合数,<1>求答案:
#include<iostream>#include<cstdio>using namespace std;long long c(int a,int b){long long ans=1;for(int i=1;i<=b;i++)ans=ans*(a-i+1)/i;return ans;}int main(){int n;long long ans;while(~scanf("%d",&n)){ans=0;for(int x=2;x<=n;x++)ans+=c(n,x)*(x-1);printf("%lld\n",ans);}return 0;}
(3)求组合数,<1>求答案:
#include<iostream>#include<cstdio>using namespace std;long long c[105][105];int main(){for(int i=-0;i<=100;i++){c[i][0]=1;for(int j=1;j<=i;j++)c[i][j]=c[i-1][j-1]+c[i-1][j];}int n;long long ans;while(~scanf("%d",&n)){ans=0;for(int x=2;x<=n;x++)ans+=c[n][x]*(x-1);printf("%lld\n",ans);}return 0;}
<3>求答案:
#include<iostream>#include<cstdio>using namespace std;long long mi2[105];int main(){mi2[0]=1;for(int i=1;i<=100;i++)mi2[i]=mi2[i-1]*2;int n;long long ans;while(~scanf("%d",&n)){ans=0;for(int x=1;x<n;x++)ans+=mi2[x-1]*(mi2[n-x]-1);printf("%lld\n",ans);}return 0;}
B RPG的错排 (HDU - 2068)
题意:
中文题。
题解:
错排数的定义:D(n)表示把n个数进行排列,每个数都不在自己原本的位置上的方案数。
这道题思路其实很清晰,从n个人中选择超过一半的人待在自己的位置上,剩下的人都不待在自己的位置上,于是答案就是((n+1)/2<=x<=n)ΣC(n,x)*D(n-x)。
组合数的部分上一题讲过了,重点说一下错排数怎么求。
如果只有1个数,错排方案为0;如果有2个数,错排方案为1。这两点都很显然。那么如果有n(n>2)个数,我们先挑1个数x放在第1个位置,显然x不会等于1,然后我们考虑第x个位置放谁,有两种情况:
(1)第x个位置放1,那么1和x就互换了位置,剩下n-2个数就重新错排,于是这种情况的方案数就等于从n个数中选一个不为1的数和1互换位置,剩下n-2个数错排,即(n-1)*D(n-2)
(2)第x个位置放的数不为1,那么相当于我们把x和1等价起来,把这n-1个数重新错排,这个x同样有n-1中选择,所以方案数是(n-1)*D(n-1)
所以当n>2,D(n)=(n-1)*(D(n-2)+D(n-1)),我们又知道D(1)和D(2)的值,就可以打错排表了。
ps:D(0)是无意义的,在这道题里面也可以认为是1,表示全对的情况。
时间复杂度o(n^2),空间复杂度o(n^2)。
代码:
#include<iostream>#include<cstdio>using namespace std;long long c[30][30],d[30];int main(){for(int i=0;i<=25;i++){c[i][0]=1;for(int j=1;j<=i;j++)c[i][j]=c[i-1][j-1]+c[i-1][j];}d[0]=1;d[1]=0;d[2]=1;for(int i=3;i<=25;i++)d[i]=(d[i-2]+d[i-1])*(i-1);int n;long long ans;while(scanf("%d",&n),n){ans=0;for(int x=(n+1)/2;x<=n;x++)ans+=c[n][x]*d[n-x];printf("%lld\n",ans);}return 0;}
C Co-prime (HDU - 4135)
题意:
给定A、B、N(1<=A<=B<=10^15,1<=N<=10^9),问区间[A,B]内有多少个数跟N互质。
T(0<T<=100)组数据。
题解:
首先我们用前缀和的思想把区间[A,B]变成[1,B]-[1,A-1]。然后我们只需要求从1到X有多少个数和N互质。按照互质的定义,我们很容易想到求每个数和N的最大公因数,看是不是1。但是很遗憾,这题A、B、N都很大。于是我们换个角度,从N入手,我们找到N的所有质因数,然后把1到X内所有含这些质因数的数都去掉就行了。这个过程中,为了避免含有多个质因数的数被重复删去,要用到容斥原理。
容斥的写法很多,这里给出队列的方法。
因为15!>10^9,所以我们可以认为N的质因数不超过15个。
时间复杂度o(T*(sqrt(N)+2^15)),空间复杂度o(2^15)。
代码:
#include<iostream>#include<cstdio>#include<queue>using namespace std;int fac[20],nums;int len1,len2;long long q1[100005],q2[100005];queue <long long> v1,v2;void init(int x){nums=0;for(int i=2;i*i<=x;i++){if(x%i==0){fac[nums++]=i;while(x%i==0) x/=i;}}if(x!=1)fac[nums++]=x;return;}long long ask(long long x){if(nums){long long ans=x-x/fac[0];len1=len2=1;q1[0]=x;q2[0]=x/fac[0];for(int i=1;i<nums;i++){for(int j=0;j<len1;j++){ans-=q1[j]/fac[i];v2.push(q1[j]/fac[i]);}for(int j=0;j<len2;j++){ans+=q2[j]/fac[i];v1.push(q2[j]/fac[i]);}while(!v1.empty()){q1[len1++]=v1.front();v1.pop();}while(!v2.empty()){q2[len2++]=v2.front();v2.pop();}}return ans;}elsereturn x;}int main(){int T;long long A,B;int N;scanf("%d",&T);for(int t=1;t<=T;t++){scanf("%lld%lld%d",&A,&B,&N);init(N);printf("Case #%d: %lld\n",t,ask(B)-ask(A-1));}return 0;}
D sum (HDU - 5776)
题意:
给定一个n(1<=n<=100000)个数的数列,第i个数为xi(1<=xi<=100),问是否存在某一段数字的和能被m(1<=m<=5000)整除。
T(1<=T<=10)组数据。
题解:
用前缀和的思想,我们很容易想到把一段数的和转化为两个前缀的差。然而枚举这两个前缀是n^2的复杂度,再考虑到多组,时间复杂度boom!
这题求的是有没有和能被m整除,那么我们把每个数变化成它除以m的余数,能被m整除就意味着余数是0,也就是两个前缀的差是0,换句话说就是两个前缀相等。
所以我们只需要看有没有多于1个前缀除以m的余数相同,或者有没有某个前缀除以m的余数直接是0,就可以得到答案了。
ps:根据抽屉原理可以得出当n>=m的时候,一定存在能被m整除的一段数的和,然并卵。。。
时间复杂度o(T*n)。
代码:
#include<iostream>#include<cstdio>#include<cstring>using namespace std;int life[5005];int main(){int t,n,m,now,nowsum;bool flag=0;scanf("%d",&t);while(t--){scanf("%d%d",&n,&m);nowsum=0;memset(life,0,sizeof(life));flag=0;for(int i=0;i<n;i++){scanf("%d",&now);nowsum=(nowsum+now)%m;if(nowsum==0 || life[nowsum])flag=1;life[nowsum]++;}if(flag)printf("YES\n");elseprintf("NO\n");}return 0;}
E GCD & LCM (ZOJ - 1577)
题意:
p和q为两个正整数,给出p和q的最大公因数x(2<=x<=100000)和最小公倍数y(2<=y<=100000),问p和q有多少种可能的组合。
多组数据。
题解:
可以把p表示为a*gcd(p,q),q表示为b*gcd(p,q),且gcd(a,b)=1。又因为lcm(p,q)=p*q/gcd(p,q)=a*b*gcd(p,q),所以a*b=lcm(p,q)/gcd(p,q),。
得到a*b了之后,求a和b有多少种组合就简单了因为a*b=lcm(p,q)/gcd(p,q),所以a*b<=100000/2,我们可以枚举能整除a*b的a,来看b和a是不是互质。
另一种思路是将a*b进行质因数分解,因为a和b互质,所以每种质因数只能属于a或者b,于是答案就是2^质因数个数。
枚举a的方法时间复杂度o(y/x),空间复杂度o(1)。
质因数分解的方法时间复杂度o(sqrt(y/x)),空间复杂度o(1)。
代码:
枚举a:
#include<iostream>#include<cstdio>using namespace std;int gcd(int a,int b){if(a%b==0)return b;elsereturn gcd(b,a%b);}int ok(int x){int ans=0;for(int i=1;i<=x;i++){if(x%i==0 && gcd(i,x/i)==1)ans++;}return ans;}int main(){int x,y;while(~scanf("%d%d",&x,&y)){if(y%x==0)printf("%d\n",ok(y/x));elseprintf("0\n");}return 0;}
质因数分解:
#include<iostream>#include<cstdio>using namespace std;int ok(int x){int ans=1,nums=0;for(int i=2;i*i<=x;i++){if(x%i==0){nums++;while(x%i==0) x/=i;}}if(x!=1)nums++;while(nums--) ans=ans*2;return ans;}int main(){int x,y;while(~scanf("%d%d",&x,&y)){if(y%x==0)printf("%d\n",ok(y/x));elseprintf("0\n");}return 0;}
F The Factor (HDU - 5428)
题意:
给出一个有n(1<=n<=100)个数的序列,第i个数为ai(1<=ai<=2*10^9),问这些数的乘积里面最小的因数个数超过2的数是多少,如果不存在输出-1。
T(1<=T<=15)组数据。
题解:
因为一个数的因数肯定包含1和本身,所以只要这个数由两个非1的正整数相乘得到,那么他肯定包括至少3个因数。于是我们只要将这个序列的每个数都进行质因数分解,从中找两个最小的因数乘起来就是答案。如果没有两个非1因数的话,那么就输出-1。
ps:注意两个大质数相乘有可能会超过int的上限。
时间复杂度o(T*n*sqrt(a)),空间复杂度o(sqrt(a)+n)。
代码:
#include<iostream>#include<cstdio>using namespace std;int ans1,ans2;void update(int x){if(x<ans1){ans2=ans1;ans1=x;}else{if(x<ans2)ans2=x;}}void go(int x){int nums;for(int i=2;i*i<=x;i++){if(x%i==0){update(i);x/=i;if(x%i==0)update(i);while(x%i==0)x/=i;}}if(x!=1)update(x);return ;}int main(){int t,n,now;scanf("%d",&t);while(t--){scanf("%d",&n);ans1=ans2=2000000005;for(int i=0;i<n;i++){scanf("%d",&now);go(now);}if(ans2>2000000000)printf("-1\n");elseprintf("%lld\n",1LL*ans1*ans2);}return 0;}
G The Euler function (HDU - 2824)
题意:
欧拉函数phi(n)表示小于n并且和n互质的数的个数,给出a和b(2<a<b<3000000),求(a<=x<=b)Σphi(x)。
题解:
一如既往的前缀和思想,把区间和转化为两个前缀的差。
欧拉函数的公式为phi(n)=n*(fac[1]-1)/fac[1]*(fac[2]-1)/fac[2]*...(fac[x]-1)/fac[x],其中fac[i]表示n的第i个质因数。
很容易想到打素数表,然后暴力求欧拉函数的每一项再求前缀和。其实我们可以把这两者结合起来,在打素数表的过程中就顺便把求欧拉函数的事干了。
ps:这题非常恶心,空间只能开的下一个long long数组,多开一个int就会mle。
时间复杂度o(3000000),空间复杂度o(3000000)。
代码:
#include<iostream>#include<cstdio>using namespace std;long long phi[3000005];int main(){for(int i=2;i<=3000000;i++)phi[i]=i;for(int i=2;i<=3000000;i++){if(phi[i]==i){for(int j=i;j<=3000000;j+=i)phi[j]=phi[j]/i*(i-1);}}for(int i=2;i<=3000000;i++)phi[i]+=phi[i-1];int a,b;long long sum=0;while(~scanf("%d%d",&a,&b))printf("%lld\n",phi[b]-phi[a-1]);return 0;}
H Raising Modulo Numbers (POJ - 1995)
题意:
有A和B两个数列,各有H(1<=H<=45000)个数,求[(1<=i<=H)ΣAi^Bi]mod M(1<=M<=45000)的值。
Z组数据。
题解:
很暴力,其实就是求(1<=i<=H)Σ[Ai^Bi mod M]。但是Ai^Bi mod M的复杂度为Bi,再乘上一个H,就会Boom!
所以我们要用快速幂来求Ai^Bi mod M。
ps:因为M^2在int范围内,所以不需要担心爆int的问题。
复杂度o(Z*H*log(Bi)),空间复杂度o(1)。
代码:
#include<iostream>#include<cstdio>using namespace std;int pows(int x,int y,int m){int ans=1;while(y){if(y%2)ans=(ans*x)%m;x=(x*x)%m;y/=2;}return ans;}int main(){int z;int a,b,ans,h,m;scanf("%d",&z);while(z--){scanf("%d%d",&m,&h);ans=0;for(int i=0;i<h;i++){scanf("%d%d",&a,&b);ans=(ans+pows(a%m,b,m))%m;}printf("%d\n",ans);}return 0;}
I Jzzhu and Sequences (CodeForces - 450B)
题意:
当i>=2数列f满足f[i]=f[i-1]+f[i+1],且f[1]=x、f[2]=y(|x|,|y|<=10^9),求f[n] mod 1000000007(1<=n<=2*10^9)。
题解:
看到f[i]=f[i-1]+f[i+1]可能会让人懵逼,不过移项一下其实就是f[i+1]=f[i]-f[i-1],所以f[i]=f[i-1]-f[i-2]。这其实就是一个变形的斐波那契数列。因为n很大所以顺序递推是行不通的,于是我们考虑使用矩阵快速幂。用一个矩阵表示线性变换,然后用快速幂的思路求矩阵的幂,最后把首项进行这个幂代表的线性变换,就是答案了。
ps:MOD*MOD会爆int,所以要用long long,还有就是可能会出现负数,注意过程中的取模。思考一下我向量的初值的含义和线性变换矩阵的含义。顺便学习一下封装一个类的思想~
时间复杂度o(log(n)),空间复杂度o(1)。
代码:
#include<iostream>#include<cstdio>using namespace std;#define MOD 1000000007struct Mat{long long num[2][2];friend Mat operator *(Mat x1,Mat x2){Mat ans;ans.num[0][0]=((x1.num[0][0]*x2.num[0][0]+x1.num[0][1]*x2.num[1][0])%MOD+MOD)%MOD;ans.num[0][1]=((x1.num[0][0]*x2.num[0][1]+x1.num[0][1]*x2.num[1][1])%MOD+MOD)%MOD;ans.num[1][0]=((x1.num[1][0]*x2.num[0][0]+x1.num[1][1]*x2.num[1][0])%MOD+MOD)%MOD;ans.num[1][1]=((x1.num[1][0]*x2.num[0][1]+x1.num[1][1]*x2.num[1][1])%MOD+MOD)%MOD;return ans;}}E;Mat pows(Mat X,int nums){Mat nowans=E;while(nums){if(nums%2)nowans=X*nowans;X=X*X;nums/=2;}return nowans;}int main(){E.num[0][0]=1;E.num[0][1]=0;E.num[1][0]=0;E.num[1][1]=1;Mat A,B;A.num[0][0]=0;A.num[0][1]=1;A.num[1][0]=-1;A.num[1][1]=1;int vec[2];int n;scanf("%d%d",&vec[0],&vec[1]);scanf("%d",&n);B=pows(A,n-1);vec[0]=((vec[0]*B.num[0][0]+vec[1]*B.num[0][1])%MOD+MOD)%MOD;printf("%d\n",vec[0]);return 0;}
J How many ways?? (HDU - 2157)
题意:
中文题。
题解:
很容易想到的其实是dp解法,不过这里提供一个用矩阵乘法来写的思路,其实原理也是dp,你们思考一下。主要是理解一下转移矩阵怎么定出来的~
ps:这题的数据,直接暴力乘就好,不用快速幂。。。
时间复杂度o(T*n^2*k),空间复杂度o(n^2)。
代码:
#include<iostream>#include<cstdio>#include<cstring>using namespace std;#define MOD 1000int n;struct Vec{int num[25];void init(){memset(num,0,sizeof(num));}friend int operator *(Vec x1,Vec x2){int ans=0;for(int i=0;i<n;i++)ans=(ans+x1.num[i]*x2.num[i])%MOD;return ans;}};Vec A[25];Vec pows(Vec x){Vec nowans;for(int i=0;i<n;i++)nowans.num[i]=A[i]*x;return nowans;}int main(){int m,s,t,T;int a,b,k;Vec ans;while(scanf("%d%d",&n,&m),n||m){for(int i=0;i<n;i++)A[i].init();while(m--){scanf("%d%d",&s,&t);A[t].num[s]=1;}scanf("%d",&T);while(T--){scanf("%d%d%d",&a,&b,&k);ans.init();ans.num[a]=1;for(int i=0;i<k;i++)ans=pows(ans);printf("%d\n",ans.num[b]);}}return 0;}