zoukankan      html  css  js  c++  java
  • GMOJ 1283排列统计 题解

    Tips:矩阵ruo

    思路

    上栗子

    A: 2 3 1
    

    首先,对于这样的一个 (A) 的排列,先把它转换成矩阵,其中第 (i) 行第 (j) 列表示第 (i) 个位置是 (j)

      数字 |   |   |   |
    位置   | 1 | 2 | 3 |
    ------+---+---+---+
        1 | 0 | 1 | 0 |
    ------+---+---+---+
        2 | 0 | 0 | 1 |
    ------+---+---+---+
        3 | 1 | 0 | 0 |
    ------+---+---+---+
    

    奇丑无比

    首先,明显的,每一行每一列都只有一个1。 废话

    然后我们注意到对于一个 (B_i) ,表示的是这个矩阵中从 ((1,1))((i,i))(1) 的个数。

    现在,假设我们要构造一个满足从 (1le jle i) 的所有 (B_j) 限制的一个 (i imes i) 的矩阵,且我们已经构造好了前 ((i-1) imes (i-1))

    比如说长这样

    0 1 ?
    0 0 ?
    ? ? ?
    

    现在 (i)(3) ,已经构造好了前((i-1) imes (i-1)) ,那么我们肯定是要在"?"处放置一些1,使得其满足 (B_i) 的限制。

    首先对于前 ((i-1) imes (i-1)) 的部分,由于它会对 (B_i) 产生贡献,且刚好满足 (B_{i-1}) 的限制,那么在"?"处放置的1的总数肯定为 (B_i-B_{i-1})

    由于可用部分一定只有一行一列,所以必然放不下 (3) 个或更多的1,于是我们只有 (3) 种情况:

    [egin{cases}0& ext{那么无操作}\1& ext{那么在任意合法位置放一个}\2& ext{那么在竖排与横排各放一个,交点不合法}end{cases} ]

    (建议把三种情况都理解了再继续)


    可以发现对于已经构造好的矩阵,其对于方案数的影响只在于其含有多少个1,位置无关紧要。而根据定义,其含有1的多少恰好就是 (B_{i-1}) 。那么,从第 (i-1) 到第 (i) 个矩阵的转移就为:

    [egin{cases} imes 1&B_i-B_{i-1}=0\ imes (2i-1-2B_{i-1})&B_i-B_{i-1}=1\ imes(i-1-B_{i-1})^2&B_i-B_{i-1}=2end{cases} ]

    (2i-1)为总位置数,(B_{i-1}) 为一行/列被占用的数量)

    那么,对于每一个矩阵,都有一样的转移,直接乘起来即可。

    实现

    记得打高精度。

    代码

    #include<cmath>
    #include<cstdio>
    #define mo 100000000
    using namespace std;
    int n,b[100010];
    struct big{
    	long long num[2000];
    	big operator *= (int x){
    		int i;
    		for(i=1;i<=num[0];i++){
    			num[i]*=x;
    		}
    		for(i=1;i<=num[0]||num[i]>0;i++){
    			num[i+1]+=num[i]/mo;
    			num[i]%=mo;
    		}
    		num[0]=i-1;
    		return(*this);
    	}
    	big(){
    		num[0]=num[1]=1;
    	}
    }ans; 
    void read(int &x){
    	char c=getchar();
    	for(;c<33;c=getchar());
    	for(x=0;(c>47)&&(c<58);x=x*10+c-48,c=getchar());
    }
    void write(big a){
    	for(int i=a.num[0];i;i--){
    		if(i<a.num[0]){
    			for(int j=mo/10;j>1;j/=10){
    				if(a.num[i]<j){
    					printf("0");
    				}
    			}
    		}
    		printf("%lld",a.num[i]);
    	}
    }
    int main(){
    	read(n);
    	for(int i=1;i<=n;i++){
    		read(b[i]);
    		if(b[i]-b[i-1]==1){
    			ans*=(2*i-1-2*b[i-1]);
    		}else if(b[i]-b[i-1]==2){
    			ans*=(i-1-b[i-1])*(i-1-b[i-1]);
    		}
    	}
    	write(ans);
    }
    

    参考

    https://blog.csdn.net/lzxzxx/article/details/38498419

  • 相关阅读:
    C#与Java的几点区别
    用VB读取记事本的内容,控制计算器的按钮(VB6)
    通过实例说明Java中的多态
    [导入]析构函数(内存泄漏,三法则)
    [导入]const限制符
    [导入]类的一些特殊限制成员
    [导入]基类的复制控制函数
    [导入]容器适配器
    [导入]派生类到基类转换的可访问性
    [导入](复制、默认)构造函数
  • 原文地址:https://www.cnblogs.com/groundwater/p/13308240.html
Copyright © 2011-2022 走看看