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问题时,质因数分解的考虑方式也值得去尝试。

 

posted @ 2018-08-23 16:00  *Miracle*  阅读(157)  评论(0编辑  收藏  举报