JZOJ5957. 【NOIP2018模拟11.7A组】scarborough fair

题意:

数据范围:

对于 15 15 15% 的数据,满足 m ≤ 21 m \leq 21 m21
对于另外 35 35 35% 的数据,满足 n ≤ 11 n \leq 11 n11
对于 80 80 80% 的数据 (包括以上 50 50 50% 的数据),满足 n ≤ 14 n \leq 14 n14
对于 100 100 100% 的数据,满足 n ≤ 17 n \leq 17 n17
时限 2 S 2S 2S

Analysis:

肯定是个状压DP。
发现点的数量其实不多,那么我们可以枚举联通块算贡献,因为期望=概率*权值。
我们枚举一个点集,使其成为联通块,且为保证不算重,此联通块一定不能再扩张,即向外的连边全部除掉。那么其它边可以任意连,外面的概率就为 1 1 1。我们要算出一个点集自成联通块的概率。
发现直接算不好算,考虑用总的减去不合法的,总概率显然为 1 1 1。设 f s f_s fs表示点集 s s s自成联通块的概率。那么我们枚举编号最小的点所在的联通块(这样不会算重,很经典的套路),然后因为要使其和剩下的点不连通,然后剩下的点之间可以任意连(概率就为 1 1 1)。
这就是一个枚举子集,发现我们还需要算跨越两个点集的边的概率乘积。
我们考虑处理出一个 g s g_s gs表示,点集 s s s互相的连边的乘积。那么若要跨越两个点集: s , s ′ s,s' s,s
我们就可以用 g s ∣ s ′ g_{s|s'} gss除掉它们内部的边,即 g s ∣ s ′ g s ∗ g s ′ \frac{g_{s|s'}}{g_s*g_s'} gsgsgss。此题就解决了。
复杂度 O ( 3 n ) O(3^n) O(3n)

Code:

# include<cstdio>
# include<cstring>
# include<algorithm>
using namespace std;
# define low(x) ((x) & (-x))
const int N = 17 + 5;
const int M = 1 << 17;
const int mo = 998244353;
typedef long long ll;
int p[N][N],e[M][2],t[M];
int n,m,ans;
inline int pow(int x,int p)
{
	int ret = 1;
	for (; p ; p >>= 1,x = (ll)x * x % mo)
	if (p & 1) ret = (ll)ret * x % mo;
	return ret;
}
inline int dec(int x,int y) { return x - y < 0 ? x - y + mo : x - y; }
inline int inc(int x,int y) { return x + y >= mo ? x + y - mo : x + y; }
int main()
{
	freopen("fair.in","r",stdin);
	freopen("fair.out","w",stdout);
	scanf("%d%d",&n,&m); int lim = 1 << n;
	for (int i = 1 ; i <= m ; ++i)
	{
		int u,v; scanf("%d%d",&u,&v);
		scanf("%d",&p[u][v]),p[v][u] = p[u][v];
	}
	for (int i = 0 ; i < lim ; ++i)
	{
		e[i][0] = 1;
		for (int j = 0 ; j < n ; ++j)
		if (i & (1 << j))
			for (int k = j + 1 ; k < n ; ++k)
			if ((i & (1 << k)) && p[j + 1][k + 1]) e[i][0] = (ll)e[i][0] * p[j + 1][k + 1] % mo;
		e[i][1] = pow(e[i][0],mo - 2);
	}
	for (int i = 0 ; i < n ; ++i) t[1 << i] = 1;
	for (int i = 2 ; i < lim ; ++i)
	{
		if (i == low(i)) continue;
		int pos = low(i),x = i - pos; t[i] = 1;
		for (int nx = (x - 1) & x ; ; nx = (nx - 1) & x)
		{
			int now = i ^ (nx | pos);
			t[i] = dec(t[i],(ll)t[nx | pos] * e[i][0] % mo * e[nx | pos][1] % mo * e[now][1] % mo);
			if (!nx) break;
		}
	}
	for (int i = 1 ; i < lim ; ++i) ans = inc(ans,(ll)t[i] * e[lim - 1][0] % mo * e[i][1] % mo * e[(lim - 1) ^ i][1] % mo);
	printf("%d\n",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值