bzoj4807 車
题目大意:
Description
众所周知,車是中国象棋中最厉害的一子之一,它能吃到同一行或同一列中的其他棋子。車跟車显然不能在一起打
起来,于是rly一天又借来了许多许多的車在棋盘上摆了起来……他想知道,在N×M的矩形方格中摆最多个数的車
使其互不吃到的情况下方案数有几种。但是,由于上次摆炮摆得实在太累,他为了偷懒,打算增加一个条件:对于
任何一个車A,如果有其他一个車B在它的上面(車B行号小于車A),那么車A必须在車B的右边(車A列号大于車B)
。
棋子都是相同的。
Input
一行,两个正整数N和M。
N<=1000000,M<=1000000
Output
一行,输出方案数的末尾50位(不足则直接输出)。
Sample Input
2 2
Sample Output
1
Solution
题目看起来狠厉害的样子,但是仔细发现,
由于增加的那个条件非常的苛刻,并且車不能互相攻击到。
所以,最优情况下,一定是左上到右下斜着放下去。
假设n>=m
发现,在最优情况下,m列一定放满了,最优答案就是m。
然后,n行可能没有放满,所以,方案数就相当于,
在n行里面选择m行放置这m个棋子的方案数。
这就是C(n,m)呀!!
所以,方案数就是C(n,m)
但是,n<=1e6,输出又要保留50位,要用高精!!
首先,我们肯定不能按照一般运算顺序,
即先算n!,再算m!,(n-m)!。
就算是你愿意高精除以低精,
但是n!就已经位数估计7e6了。再循环n次?不可能。
但是,最后就要保留50位啊,那n!就每次取个余数,然后再用这个余数除以m!
纯粹瞎搞。取余运算哪里有这个运算律??
那么怎么办?
发现,困扰的地方就是除法。
如果只有乘法,没有除法,那么每次取最后50位就是可行的了。
怎么避免除法?
组合数一定是一个整数。所以n!的质因数分解后,肯定能消完分母的质因子分解情况。
先化简一下:
C(n,m)=(m+1)*.....*n/(1*....*(n-m)
线性筛素数。
把分母1~n-m依次用质数根号n质因数分解。
每个质因子维护一个桶,记录在分母中出现的次数。
之后,循环分子i,
把分子质因数分解,用分母中的质因子桶能消除一些质因子就消除。
最后留下的i可以高精乘进去。
(可以采用压位高精,快10倍)
虽然理论nsqrt(n),但是由于用质数分解更快,而且小数不用循环sqrt(n)次。
所以O(能过)
Code
/************************************************************** Problem: 4807 User: 20011023 Language: C++ Result: Accepted Time:1332 ms Memory:17892 kb ****************************************************************/ #include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1000000+5; const ll mod=1e10; int n,m; int k; int exi[N]; int ans; int yue[N],has[N],cnt; int pri[N],tot; bool vis[N]; void sieve(){ for(int i=2;i<=n;i++){ if(!vis[i]){ pri[++tot]=i; } for(int j=1;j<=tot;j++){ if(pri[j]*i>n) break; vis[pri[j]*i]=1; if(i%pri[j]==0) break; } } } void div1(int x){ for(int i=1;pri[i]*pri[i]<=x;i++){ while(x%pri[i]==0){ exi[i]++; x/=pri[i]; } } if(x>1){ int k=lower_bound(pri+1,pri+tot+1,x)-pri; exi[k]++; } } int div2(int x){ int now=x; for(int i=1;pri[i]*pri[i]<=x;i++){ if(now%pri[i]==0){ while(now%pri[i]==0){ if(exi[i]){ exi[i]--; x/=pri[i];} now/=pri[i]; } } } if(now>1){ int k=lower_bound(pri+1,pri+tot+1,now)-pri; if(exi[k]){ exi[k]--; x/=pri[k]; } } return x; } struct big{ ll a[10]; int cnt; void pre(){ cnt=1,a[1]=1; } void mul(const ll &x){ int ji=0; for(int i=1;i<=cnt;i++){ a[i]=a[i]*x+ji; ji=a[i]/mod; a[i]%=mod; } if(ji){ a[++cnt]=ji; } if(cnt>5) cnt=5; } void op(){ while(a[cnt]==0&&cnt>1) cnt--; printf("%lld",a[cnt--]); while(cnt)printf("%010lld",a[cnt--]); } }A; void wrk(){ A.pre(); for(int i=m+1;i<=n;i++){ int re=div2(i); A.mul(re); } } int main() { scanf("%d%d",&n,&m); if(m>n) swap(n,m); if(n==m){ printf("1");return 0; } else if(n==1){ printf("%d",m);return 0; } else if(m==1){ printf("%d",n);return 0; } sieve(); for(int i=1;i<=n-m;i++){ div1(i); } wrk(); A.op(); return 0; }
Conclusion
质因数分解往往可以起到意想不到的优化,因为一个数的分解呈现了它的本质。
决定了一切的乘除,gcd,找因数等运算。
1.乘除,本质上是质因子次数的加减。
2.gcd本质上是所有质因子次数取min再相乘。lcm则取max
3.因数的产生本质上是一个乘法原理。质因子次数的选择也决定了因数的数值。
并且,处理一些gcd问题时,质因数分解的考虑方式也值得去尝试。