Task
:
给定
N(5<=N<=14)
个数分别为
0,1,⋅⋅⋅,n−1
,对这
N
个数进行排列,同时有
Solution
:
几乎是一道裸的状压dp题(虽然我没有看出来)。我们定义
dp[Set][pre]
表示当前还没有选的元素集合为
Set
,上一次挑选的元素为
pre
。
首先考虑递归的做法:
对于当前层
Set
,我们可以从
Set
中挑选1个元素作为下一层的
pre
。那么一开始我们可以调用
f(Alllanes,n)
表示当前所有的元素都可以调用,并且还未选任何元素,因为我们已经要求所有元素的下标范围为
[0,n−1]
。
在递归调用中就只会出现两种情况:
1.最基本的情况,
Set=∅
,这意味着所有元素已经被取光。这种情况唯一的排列就是空排列,并且没有被ban掉的情况,那么概率就是1.0;
2.中间情况,在
Set
中有
cnt
个元素,每个元素被挑选的概率为
1cnt
,那么每个子状态的概率加权的值就是当前层的概率了。
再考虑到有ban的要求,那么如果
(pre,x)
是一组被ban的数对,那么这个子状态的可能性就为0.0;
根据以上分析,就可以轻松写出这个裸状压dp:
class RandomOption {
public:
int Ban[15][15];
int All_lane,n;
double dp[1<<14][15];
double rec(int Set_lane,int pre){
double &res=dp[Set_lane][pre];
if(res)return res;
int cnt=0;
for(int x=0;x<n;x++)
if(Set_lane>>x&1){
if(!Ban[pre][x])
res+=rec(Set_lane-(1<<x),x);
cnt++;
}
if(!cnt)res=1.0;
else res/=1.0*cnt;
return res;
}
double getProbability(int keyCount,vector<int> bad1,vector<int> bad2) {
for(int i=0;i<bad1.size();i++){
int u=bad1[i],v=bad2[i];
Ban[u][v]=Ban[v][u]=1;
}
n=keyCount,All_lane=(1<<n)-1;//0 ~ 14
return rec(All_lane,n);
}
};
接下来根据递归的思路写出递推:
由于每一层的状态都是 pair(Set,pre) , Set 始终是含有 keyCount 个元素的全集的子集,所以枚举状态的复杂度是 O(2keyCount∗keyCount) 。接下来枚举要转移的元素To,复杂度是 O(keyCount) ,所以总复杂度是喜闻乐见的 O(2n∗n2) 。要注意的是本题中 keyCount 的范围只有14,所以这样考虑正合适。
class RandomOption {
public:
int Ban[15][15];
int All_lane,n;
double dp[1<<14][15];
double getProbability(int keyCount,vector<int> bad1,vector<int> bad2) {
for(int i=0;i<bad1.size();i++){
int u=bad1[i],v=bad2[i];
Ban[u][v]=Ban[v][u]=1;
}
n=keyCount,All_lane=(1<<n)-1;
for(int i=0;i<n;i++)dp[0][i]=1.0;//特判Set为空集的情况
for(int S=1;S<All_lane;S++){
int cnt=0;
for(int i=0;i<n;i++)if(S>>i&1)cnt++;
for(int pre=0;pre<n;pre++){
if(S>>pre&1)continue;
for(int to=0;to<n;to++){
if((S>>to&1)&&!Ban[pre][to])
dp[S][pre]+=dp[S-(1<<to)][to]/(1.0*cnt);
}
}
}
for(int to=0;to<n;to++)//特判Set为全集的情况
dp[All_line][n]+=dp[All_line-(1<<to)][to]/(1.0*keyCount);
return dp[All_lane][n];
}
};