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

87 篇文章 0 订阅
11 篇文章 0 订阅

在这里插入图片描述

Data Constraint

对于 15% 的数据,满足 m ≤ 21。
对于另外 35% 的数据,满足 n ≤ 11。
对于 80% 的数据 (包括以上 50% 的数据),满足 n ≤ 14。
对于 100% 的数据,满足 n ≤ 17。

题解

看到数据范围,就可以连续到状态压缩。
很显然每个联通块对答案的贡献都是独立的,所以只需要知道每个联通块的连通概率就可以了。
f s f_s fs表示s这个二进制状态的点集不连通。
那么1- f s f_s fs就表示这个点集形成联通块连通的概率。
考虑转移,
枚举一个s的子集ss,
f s = ∑ ( 1 − f s s ) ∗ 集 合 s s 与 集 合 s 的 其 他 点 都 不 连 通 个 概 率 f_s=\sum (1-f_{ss})*集合ss与集合s的其他点都不连通个概率 fs=(1fss)sss
为了避免重复就是,ss应该要保证一定选了s里面编号最小的点。
于是现在的关键就在于如何求这个概率。
g s g_s gs表示s这个点集里面的边全部不连通的概率,
那么就可以用g来表示出这个概率了
g s / g s s / g s s 补 集 g_s/g_{ss}/g_{ss补集} gs/gss/gss
这个就是s其他点与ss之间全部边不连通的概率了。
最后统计答案,
枚举点集s,使得s连通,而且s不与其他非s集合的点有连边,
这个情形跟转移很类似。

code

#pragma GCC optimize(2)
#pragma G++ optimize(2)
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N=23,mo=998244353,M=131073;
int n,m,x,y,z,nxt[N*N],to[N*N],v[N*N],vv[N*N],lst[N],tot;
int f[M],g[M],ny[M],_2[N],ans;
bool bz[M];
char ch;
void read(int&n)
{
	for(ch=getchar();ch<'0' || ch>'9';ch=getchar());
	for(n=0;'0'<=ch && ch<='9';ch=getchar())n=(n<<1)+(n<<3)+ch-48;
}
int x_(int x){return (-x)&x;}
int ksm(ll x,int y)
{
	ll s=1;
	for(;y;y>>=1,x=x*x%mo)
		if(y&1)s=s*x%mo;
	return s;
}
void ins(int x,int y,int z)
{
	nxt[++tot]=lst[x];
	to[tot]=y;v[tot]=z;vv[tot]=ksm(z,mo-2);
	lst[x]=tot;
}
int add(int x,int y){return x+y<mo?x+y:x+y-mo;}
int main()
{
	freopen("fair.in","r",stdin);
	freopen("fair.out","w",stdout);
	_2[0]=1;for(int i=1;i<N;i++)_2[i]=_2[i-1]<<1;
	read(n);read(m);
	for(int i=1;i<=m;i++)
	{
		read(x),read(y),read(z);
		if(x^y)ins(x,y,z),ins(y,x,z);
	}
	g[0]=ny[0]=1;
	for(int s=1;s<_2[n];s++)
	{
		for(int i=1;i<=n;i++)
			if(s&_2[i-1])
			{
				g[s]=g[s^_2[i-1]];ny[s]=ny[s^_2[i-1]];
				for(int j=lst[i];j;j=nxt[j])
					if(s&_2[to[j]-1])g[s]=(ll)g[s]*v[j]%mo,ny[s]=(ll)ny[s]*vv[j]%mo;
				break;
			}
	}
	for(int s=0;s<_2[n];s++)
	{
		for(int ss=s;ss;ss=(ss-1)&s)
		{
			if(s==ss || (x_(s)&ss)==0 || ss==0)continue;
			f[s]=(f[s]+(ll)(mo+1-f[ss])*ny[ss]%mo*ny[s^ss])%mo;
		}
		f[s]=(ll)f[s]*g[s]%mo;
	}
	for(int s=1;s<_2[n];s++)
		ans=(ans+(ll)(mo+1-f[s])*g[_2[n]-1]%mo*ny[s]%mo*ny[(_2[n]-1)^s])%mo;
	printf("%d\n",ans);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值