題目鏈接:
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;
}