[UOJ#348][WC2018]州区划分(状压dp+FMT)

Address

洛谷P4221
BZOJ5153
UOJ#348
LOJ#2340

Solution

有一个显然的 dp 方案:
f [ S ] f[S] f[S] 表示选出的城市集合为 S S S 的满意度之和。
s u m [ S ] sum[S] sum[S] 表示城市集合 S S S 的人口之和。
g [ S ] g[S] g[S] 表示:
[ 子 图 S 不 存 在 欧 拉 回 路 ] × s u m [ S ] p [子图S不存在欧拉回路]\times sum[S]^p [S]×sum[S]p
转移显然:
f [ S ] = 1 s u m [ S ] p ∑ T ⊊ S f [ T ] g [ S − T ] f[S]=\frac1{sum[S]^p}\sum_{T\subsetneq S}f[T]g[S-T] f[S]=sum[S]p1TSf[T]g[ST]
复杂度 O ( 3 n ) O(3^n) O(3n) ,难以通过 n = 21 n=21 n=21 的数据。
考虑到这是子集卷积的形式,不妨把 f f f 按照 S S S 集合大小分类:
f [ i ] [ S ] f[i][S] f[i][S] 表示:
{ f [ S ] ∣ S ∣ = i 0 ELSE \begin{cases}f[S]&|S|=i\\0&\text{ELSE}\end{cases} {f[S]0S=iELSE
那从小到大枚举 i i i ,再枚举一个 0 ≤ j &lt; i 0\le j&lt;i 0j<i ,那么 f [ i ] f[i] f[i] 就能从 f [ j ] f[j] f[j] g [ i − j ] g[i-j] g[ij] 转移。
如果已经求得了 f [ 0... i − 1 ] f[0...i-1] f[0...i1] 的快速莫比乌斯变换 f [ 0 ] ′ , f [ 1 ] ′ , . . . , f [ i − 1 ] ′ f[0]&#x27;,f[1]&#x27;,...,f[i-1]&#x27; f[0],f[1],...,f[i1]
以及 g g g 的 FMT g ′ g&#x27; g ,
那么就可以直接计算 f [ i ] ′ [ S ] + = f [ j ] ′ [ S ] × g [ i − j ] ′ [ S ] f[i]&#x27;[S]+=f[j]&#x27;[S]\times g[i-j]&#x27;[S] f[i][S]+=f[j][S]×g[ij][S]
反演回去之后将每个 f [ i ] [ S ] f[i][S] f[i][S] 除以 s u m [ S ] p sum[S]^p sum[S]p
注意需要将 ∣ S ∣ ≠ i |S|\ne i S̸=i f [ i ] [ S ] f[i][S] f[i][S] 清零。
复杂度 O ( 2 n × n 2 ) O(2^n\times n^2) O(2n×n2)

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
#define Subset(k, i) for (k = i; k; k = (k - 1) & i)
using namespace std;

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

const int N = 25, C = (1 << 21) + 5, MOD = 998244353;

int qpow(int a, int b)
{
	int res = 1;
	while (b)
	{
		if (b & 1) res = 1ll * res * a % MOD;
		a = 1ll * a * a % MOD;
		b >>= 1;
	}
	return res;
}

int n, m, p, Cm, w[N], cnt[N], f[N][C], sum[C], inv[C], ans, eul[N][C],
sze[C], fa[N];
bool gr[N][N], cir[C];

void FWT(int n, int *a, int op)
{
	int i, j;
	For (i, 1, n) For (j, 0, (1 << n) - 1)
		if (!((j >> i - 1) & 1))
			a[j | (1 << i - 1)] = (a[j | (1 << i - 1)]
				+ (op == 1 ? a[j] : MOD - a[j])) % MOD;
}

int fd(int x)
{
	if (fa[x] != x) fa[x] = fd(fa[x]);
	return fa[x];
}

void mg(int x, int y)
{
	int ix = fd(x), iy = fd(y);
	if (ix != iy) fa[iy] = ix;
}

int main()
{
	int i, j, S, x, y;
	n = read(); m = read(); p = read();
	Cm = (1 << n) - 1;
	For (i, 1, m) x = read(), y = read(),
		gr[x][y] = gr[y][x] = 1;
	For (i, 1, n) w[i] = read();
	For (S, 1, Cm)
	{
		For (i, 1, n) cnt[i] = 0, fa[i] = i; int xp = 0;
		For (i, 1, n) if ((S >> i - 1) & 1)
		{
			sum[S] += w[i]; sze[S]++; xp = i;
			For (j, i + 1, n) if (((S >> j - 1) & 1) && gr[i][j])
				cnt[i]++, cnt[j]++, mg(i, j);
		}
		sum[S] = qpow(sum[S], p);
		inv[S] = qpow(sum[S], MOD - 2);
		For (i, 1, n) if (((S >> i - 1) & 1) && fd(i) != fd(xp))
			{cir[S] = 1; continue;}
		For (i, 1, n) if (cnt[i] & 1)
			{cir[S] = 1; continue;}
	}
	For (i, 0, Cm) eul[sze[i]][i] = cir[i] * sum[i];
	For (i, 0, n) FWT(n, eul[i], 1);
	f[0][0] = 1;
	FWT(n, f[0], 1);
	For (i, 1, n)
	{
		For (j, 0, i - 1) For (S, 0, Cm)
			f[i][S] = (f[i][S] + 1ll * f[j][S] * eul[i - j][S] % MOD) % MOD;
		FWT(n, f[i], -1);
		For (S, 0, Cm)
			if (sze[S] == i) f[i][S] = 1ll * f[i][S] * inv[S] % MOD;
			else f[i][S] = 0;
		FWT(n, f[i], 1);
	}
	FWT(n, f[n], -1);
	cout << f[n][Cm] << endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值