C-奇奇怪怪的魔法阵 折半枚举做法

奇奇怪怪的魔法阵

https://ac.nowcoder.com/acm/contest/11193/C

出题人的题解很简洁,直接dp(S)表示S集合内有多少个独立集子集,然后考察S中随便一个元素i的情况,由之前状态转移过来,复杂度(2^n)

但是事实上这道题可以通过折半枚举的方法优化到(n * 2^(n/2))。

首先,按照题目中的说法一个一个求每个集合T内独立子集个数肯定不能找到更快的算法,因为此时枚举每个集合就要(2^n),复杂度不会低于这个下界。因此,考虑将最终答案的式子化简一下:

alt

就是先交换一下枚举顺序,再把后面一项打开

那么现在问题就是对于每个独立集,计算它对最终答案贡献的一个式子,同样考虑dp

为了方便dp,此时我们可以把点i的权值val(i)记为

alt

然后转移时,枚举到集合S,先判断这个S是否可行,如果可行,对于dp(S),就可以随便枚举一个元素i,令dp(S) = dp(S - {i}) * val[i],最终统计答案时每个dp(S)乘上一个 bas = alt 后加入答案即可

之后发现这个dp是可以用折半枚举优化的

因为考虑一个是独立集的集合S,我们处理出#S集合内所有点#的邻接点点集cant,那么补集N-S中一个集合T能并上S仍然是独立集,当且仅当T是独立集且T交cant为空集。

而根据dp的转移式子,dp[S * T] = dp[S] * dp[T],运用类似前缀和优化的技巧,我们就可以先dp处理前一半点的集合,求出dp值并把它的贡献加入答案中,然后对这一半的dp数组求集合前缀和(即每个位置的值加上所有它的子集位置值),记入sum数组。

然后,再类似第一遍求后一半dp值,dp同时统计与之前一半合并的贡献。假设当前枚举到S, 它的邻接点为cant_S,则可以得到这个S与之前一半的集合合并会产生的贡献为dp[S] * sum[{前一半集合} - cant_S]

折半枚举优化后,复杂度瓶颈在集合前缀和那一步,用fwt集合并卷积的正变换,复杂度为O(n * 2^(n/2))

#define pii pair<int,int>
#define fi first
#define sc second
#define pb push_back
#define ll long long
#define trav(v,x) for(auto v:x)
#define all(x) ((x).begin(), (x).end())
#define VI vector<int>
#define VLL vector<ll>
using namespace std;
const int N = 26;
const int inf = 1e9;
const int mod = 1e9 + 7;

int n, m;
int a[N];

int hav[N], val[N], dp[1 << 14], sum[1 << 14];

int qpow(int x, int y)
{
	int res = 1;
	while(y)
	{
		if(y & 1)
			res = 1LL * res * x % mod;
		x = 1LL * x * x % mod;
		y >>= 1;
	}
	return res;
}

signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> m;
	for(int i = 1; i <= m; i++)
	{
		int x, y;
		cin >> x >> y;
		hav[x] |= (1 << y);
		hav[y] |= (1 << x);
	}
	int bas = 1;
	for(int i = 0; i < n; i++)
		bas = 1LL * bas * (qpow(233, 1 << i) + 1) % mod;
	for(int i = 0; i < n; i++)
	{
		val[i] = qpow(233, 1 << i);
		val[i] = 1LL * val[i] * qpow(qpow(233, 1 << i) + 1, mod - 2) % mod;
	}
	int mid = n / 2;
	int ans = 0;
	int lim = 1 << mid;
	for(int S = 0; S < lim; S++)
	{
		if(S == 0)
		{
			dp[S] = bas;
			ans = (ans + dp[S]) % mod;
			continue;
		}
		int cant = 0;
		for(int i = 0; i < mid; i++)
		{
			if(S & (1 << i))
				cant |= hav[i];
		}
		if(cant & S)
			continue;
		int las = S & (-S), ps = __builtin_ffs(S) - 1;
		dp[S] = 1LL * dp[S - las] * val[ps] % mod;
		ans = (ans + dp[S]) % mod;
	}
	for(int l = 2, md = 1; l <= lim; l <<= 1, md <<= 1)
       for(int i = 0; i < lim; i += l)
           for(int j = 0; j < md; j++)
               dp[i + j + md] = (dp[i + j + md] + dp[i + j]) % mod;
    memcpy(sum, dp, sizeof dp);
    memset(dp, 0, sizeof dp);
    lim = (1 << (n - mid));
    dp[0] = 1;
    for(int S = 1; S < lim; S++)
	{
		int cant = 0, cantl, cantr;
		for(int i = 0; i < (n - mid); i++)
		{
			if(S & (1 << i))
				cant |= hav[i + mid];
		}
		cantl = cant & ((1 << mid) - 1);
		cantr = cant - cantl;
		cantr >>= mid;
		if(cantr & S)
			continue;
		int las = S & (-S), ps = __builtin_ffs(S) - 1;
		dp[S] = 1LL * dp[S - las] * val[ps + mid] % mod;
		ans = (ans + 1LL * dp[S] * sum[(1 << mid) - 1 - cantl]) % mod;
	}
	cout << ans << '\n';
	return 0;
}


全部评论

相关推荐

找不到工作死了算了:没事的,雨英,hr肯主动告知结果已经超越大部分hr了
点赞 评论 收藏
分享
评论
2
收藏
分享
牛客网
牛客企业服务