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 |
------+---+---+---+
奇丑无比
首先,明显的,每一行每一列都只有一个数。 废话
然后我们注意到对于一个 \(B_i\) ,表示的是这个矩阵中从 \((1,1)\) 到 \((i,i)\) 中 \(1\) 的个数。
现在,假设我们要构造一个满足从 \(1\le j\le i\) 的所有 \(B_j\) 限制的一个 \(i\times i\) 的矩阵,且我们已经构造好了前 \((i-1)\times (i-1)\)
比如说长这样
0 1 ?
0 0 ?
???
现在 \(i\) 为 \(3\) ,已经构造好了前\((i-1)\times (i-1)\) ,那么我们肯定是要在"?"处放置一些1,使得其满足 \(B_i\) 的限制。
首先对于绿色部分,由于它会对 \(B_i\) 产生贡献,且刚好满足 \(B_{i-1}\) 的限制,那么在"?"处放置的1的总数肯定为 \(B_i-B_{i-1}\)
由于可用部分一定只有一行一列,所以必然放不下 \(3\) 个或更多的1,于是我们只有 \(3\) 种情况:
\[\begin{cases}0&\text{那么无操作}\\1&\text{那么在任意合法位置放一个}\\2&\text{那么在竖排与横排各放一个,交点不合法}\end{cases} \]
(建议把三种情况都理解了再继续)
可以发现对于已经构造好的矩阵,其对于方案数的影响只在于其含有多少个1,位置无关紧要。而根据定义,其含有1的多少恰好就是 \(B_{i-1}\) 。那么,从第 \(i-1\) 到第 \(i\) 个矩阵的转移就为:
\[\begin{cases}\times 1&B_i-B_{i-1}=0\\\times (2i-1-2B_{i-1})&B_i-B_{i-1}=1\\\times(i-1-B_{i-1})^2&B_i-B_{i-1}=2\end{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);
}
标签:------+---+---+---+,1283,int,题解,矩阵,times,num,cases,GMOJ 来源: https://www.cnblogs.com/groundwater/p/13308240.html