题目链接:
http://codeforces.com/contest/341/problem/C
题目大意:
给定一个含有N个位置的序列,某些位置上的数字已经确定,某些位置上的数字没有确定。
求这个序列可能产生多少种错排(a[i] != i)。
算法:
这题的做法基本就是DP或容斥。
先说一下DP的解法。感谢wuyiqi大牛的耐心讲解。 >_<
显然所有已经填好的位置可以不管,我们只看 a[i] = -1 的位置。
x 表示目前有多少个位置使得a[i] = -1 且数字 i 已经被填在某个位置上,称为无限制位置。
y 表示目前有多少个位置使得a[i] = -1 且数字 i 没有被填在某个位置上,称为有限制位置。
那么,最一开始的时候,我们先把 y 个有限制位置抛弃不看。
因为a[i] = -1 且 i 也没有出现在任何位置上,所以忽略不看不会有影响。
现在我们先把 x 个无限制的位置填好。
由于有 x 个数的 a[i] = -1 但是数字 i 已经被使用了。所以也一定有 x 个数的a[i] != -1但是数字 i 还没被用。
就把这 x 个没使用的数字放在 x 个被填的位置上。方法数是 x! 。
现在我们把 y 个无限制的位置一个接一个的加进来
用 d[i] 表示已经有多少个无限制的位置被加进来,且不违反错排的规则。
显然d[0] = x ! 。
如果此时我们把第 i 个有限制的位置加进来。分以下几种情况:
1)我们从 x 个无限制的的位置中找一个 j ,令a[i] = a[j],a[j] = i。规约到d[i - 1]的方案数。
2)我们从i - 1个有限制的位置中找一个位置 j ,令a[i] = j,a[j] = i。规约到d[i - 2]的方案数。
3)我们从i - 1个有限制的位置中找一个位置 j ,令a[i] = j,但是a[j] != i。规约到d[i - 1]的方案数。也就相当于由原来的 d[j] != j 限制变为了d[j] != i,其它限制不变。
容斥的做法,就是枚举至少有多少个 i 使得a[i] = i,具体可以看ftiasch的代码。
PS:
这题对我来说还算是稍有意义。
因为这场比赛我的后一个半小时都在想这个。也没别的事好干。
这种感觉简直太可怕了。QAQ 若菜一把辛酸泪啊
代码:
#include <cstdio> #include <iostream> #include <algorithm> #include <sstream> #include <cstdlib> #include <cstring> #include <string> #include <climits> #include <cmath> #include <queue> #include <vector> #include <stack> #include <set> #include <map> #define INF 0x3f3f3f3f #define eps 1e-8 using namespace std; const int MOD = 1000000007; const int MAXN = 2100; int a[MAXN]; bool hash[MAXN]; long long d[MAXN]; int main() { int n; scanf("%d", &n); memset(hash, 0, sizeof(hash)); for(int i = 0; i < n; i ++) { scanf("%d", &a[i]); if(a[i] != -1) { hash[a[i]] = true; } } int x = 0, y = 0; for(int i = 0; i < n; i ++) { if(a[i] == -1) { if(hash[i + 1]) { x ++; } else { y ++; } } } d[0] = 1; for(int i = 1; i <= x; i ++) { d[0] = d[0] * i % MOD; } for(int i = 1; i <= y; i ++) { d[i] = (x + i - 1) * d[i - 1] % MOD; if(i > 1) { d[i] = (d[i] + (i - 1) * d[i - 2]) % MOD; } } printf("%I64d\n", d[y]); return 0; }