CodeForces - 1842H
*3000,大牛题。
分析
题目的转化
首先题目中 \(x_i+x_j \le 1\) 和 \(x_i+x_j\ge 1\) 不好处理,我们不妨将 \(x_i\) 都减去 \(0.5\),结果不变,那么原题则转化成了 \(x_i+x_j\le or \ge 0\),发现现在只在乎 \(x_i\) 之间的绝对值大小关系与正负,这样就可以求出总方案数和合法的方案数,所以我们转化到了一个经典的计数问题。
动态规划
考虑按绝对值从小到大加入,首先有 \(O(n3^n)\) 的做法,设 \(dp_{S}\) 为每个数为负数正数或还没被填。
考虑枚举新添的一位,如果满足限制转移即可。
考虑优化,考虑题目性质能否帮我们优化状态,注意限制,发现 当 \(|a_i|<|a_j|\) 时,若 \(a_i+a_j\ge0\),则与 \(a_i\) 无关,\(a_j\) 必然大于 0。若 \(a_i+a_j\le 0\),则 \(a_j\) 必然小于 \(0\)。因此可以枚举当前数的正负转移即可,状态变为了元素是否在当前集合,时间复杂度降为 \(O(n2^n)\)
代码
int n,m,dp[(1<<N)],lim[N][2];
//0 小于,1 大于
void Main(){
n=rd,m=rd,dp[0]=1;
for(int i=1;i<=m;i++){
int opt=rd,x=rd-1,y=rd-1;
lim[x][opt]|=(1<<y),lim[y][opt]|=(1<<x);
}
for(int S=1;S<(1<<n);S++){
for(int i=0;i<n;i++){
if(!(S>>i&1)) continue;
for(int j=0;j<2;j++){
if(lim[i][1-j]&S) continue;
dp[S]=(dp[S]+dp[S^(1<<i)])%mod;
}
}
}
int ans=dp[(1<<n)-1];
for(int i=1;i<=n;i++) ans=ans*qmi(i,mod-2)%mod;
ans=ans*qmi(inv2,n)%mod;
cout<<ans<<endl;
}