序列个数
题目链接:ybt高效进阶强化训练1-1-4
题目大意
给你一个序列 ai,问你有多少个排列满足对于每个位置 i,都有排列 1~i 的位置恰好有 ai 个小于等于 i 的数。
思路
首先我们考虑把它弄成一个一维平面,每个位置
0
/
1
0/1
0/1,如果
(
i
,
j
)
(i,j)
(i,j) 是
1
1
1 就代表第
i
i
i 个位置用的是
j
j
j 这个数。
不难看出每行每列只能有
1
1
1 个
1
1
1,而且
a
i
a_i
ai,就是
(
1
,
1
)
(1,1)
(1,1) 到
(
i
,
i
)
(i,i)
(i,i) 这个矩阵中
1
1
1 的个数。
那我们不难想到把它从里到外考虑,每次就多出来一个这样的形状。
然后不难想到是通过看
a
i
−
a
i
−
1
a_i-a_{i-1}
ai−ai−1 的值来转移。
那可以看到你顶多多放两个,那如果要放的超过两个或是负数那肯定是没有解,方案数直接是
0
0
0。
如果是两个,就是下面新的和右边新各选一个位置。
看似是
(
i
−
1
)
2
(i-1)^2
(i−1)2,但其实我们还要考虑前面的选的行 / 列我们就不能选了,那前面选的个数是
a
i
−
1
a_{i-1}
ai−1,那就要减去,就是
(
i
−
1
−
a
i
−
1
)
2
(i-1-a_{i-1})^2
(i−1−ai−1)2。
那同理,如果是 1 1 1 个,那就是这些所有位置中选一个,再减去之前选的影响的,就是 2 i − 1 − 2 a i − 1 2i-1-2a_{i-1} 2i−1−2ai−1。
然后把这些乘起来,就是答案。
代码
#include<cstdio>
#define mo 340610
#define ll long long
using namespace std;
int n, a[10001];
ll ans;
int main() {
scanf("%d", &n);
ans = 1;
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
if (a[i] - a[i - 1] < 0 || a[i] - a[i - 1] > 2) {
printf("0");
return 0;
}
if (a[i] - a[i - 1] == 0) continue;
if (a[i] - a[i - 1] == 1) ans *= (2 * i - 1) - 2 * a[i - 1];
if (a[i] - a[i - 1] == 2) ans *= 1ll * ((i - 1) - a[i - 1]) * ((i - 1) - a[i - 1]) % mo;
ans %= mo;
}
printf("%lld", ans);
return 0;
}