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;
}
posted @ 2024-07-25 18:35  SmileMask  阅读(8)  评论(0编辑  收藏  举报