『MdOI R4』Phoenix 官解(也许)更清晰的阐释

\[\large(\sum\limits_{i=1}^n |s_i|)-(\sum\limits_{i=1}^{n-1} |s_{p_i}\bigcap s_{p_{i+1}}|)=|\bigcup\limits_{i=1}^n s_i| \]

观察题目中式子,不难想到如果对二进制拆位,那么相当于要求对于每个二进制位,包含这一位的集合必须排列在一段区间内,因为左式中每一位至少出现一次,而右式限制了至多出现一次,要让这一位只出现一次就只能排列在区间内

那么现在我们得到了 \(m\) 个区间,容易发现对于两个相交的区间,它们的顺序就会固定下来,或者倒过来,有两种方案,而完全包含的区间的限制就是挖掉一些段,剩下的任意排列,再对挖掉的段选位置放

如果这时直接用 \(PQ\ Tree\) 来做就可以得到一个 \(O(nm)\) 的做法,但是比较复杂,这里不赘述

考虑用并查集合并区间,然后对于包含关系建出一棵树

对于相交的区间,我们可以任意排列的部分实际上是下图所示分裂出来的三个区域

在相交区间合并的过程中维护这些分裂出来的段就可以得到所有能任意排列的区间,定义 \(cnt\) 为最后 \(size>1\) 的连通块数,最后分裂出来了 \(x_1,x_2,\dots,x_k\) 段,包含区间的树形结构上的答案是 \(val\),则最后的答案就是:

\[\large2^{cnt}\times val\times \prod\limits_{i=1}^kx_i! \]

\(cnt\) 可以用并查集统计,\(val\) 是 trivial 的组合问题,关键在于求后面的 \(\prod\)

\(PQ\ Tree\) 强行完成了这个分裂的过程,但是我们并不需要用数据结构,观察到对于每一个位,加入它对应的区间实际上进行了对所有集合是否包含这个位的区分,那么最终分裂下来的这些段,就一定会是一个个相同的集合

所以用一个 \(map\) 就可以轻松地解决这个问题

具体实现还是有一些细节

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define N 100005
using namespace std;
int read(){
	int w=0,h=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')h=-h;ch=getchar();}
	while(ch>='0'&&ch<='9'){w=w*10+ch-'0';ch=getchar();}
	return w*h;
}
const int mod=998244353;
int n,m,ans,a[N];
int Fac[N],IFac[N];
unordered_map<int,int>M,P;
int anc[64],in[64],vec[64],sec[64][64];
int qpow(int b,int k){int s=1;while(k){if(k&1)s=s*b%mod;b=b*b%mod;k>>=1;}return s;}
namespace DSU{
	int fa[N],siz[N];
	int Find(int x){return x==fa[x]?x:fa[x]=Find(fa[x]);}
	void Union(int x,int y){
		if((x=Find(x))==(y=Find(y)))return;
		if(siz[x]>siz[y])swap(x,y);
		siz[fa[x]=y]+=siz[x];
	}
}
using namespace DSU;
void solve(){
	n=read();m=read();ans=1;M.clear();P.clear();
	for(int i=0;i<m;i++)
		for(int j=0;j<m;j++)sec[i][j]=vec[i]=anc[i]=0;
	for(int i=1;i<=n;i++)(ans*=++M[a[i]=read()])%=mod;
	sort(a+1,a+n+1);n=unique(a+1,a+n+1)-a-1;
	for(int i=1;i<=n;i++)
		for(int j=0;j<m;j++)
			if((a[i]>>j)&1)vec[j]+=M[a[i]];
	for(int i=1;i<=n;i++)
		for(int j=0;j<m;j++)
			for(int k=0;k<m;k++)
				if(j!=k&&((a[i]>>j)&1)&&((a[i]>>k)&1))sec[j][k]+=M[a[i]];
	for(int i=0;i<m;i++)siz[fa[i]=i]=1;
	for(int i=0;i<m;i++)
		for(int j=i+1;j<m;j++)
			if(sec[i][j]==vec[i]&&sec[i][j]==vec[j])siz[j]=0;
	for(int i=0;i<m;i++)
		for(int j=i+1;j<m;j++)
			if(sec[i][j]>0&&sec[i][j]<min(vec[i],vec[j]))Union(i,j);
	for(int i=0;i<m;i++)
		for(int j=0;j<m;j++)
			if(Find(i)!=Find(j)&&vec[i]>vec[j]&&sec[i][j]==vec[j])anc[j]|=(1ll<<i);
	for(int u=0;u<m;u++)
		if(u==Find(u)&&siz[u])P[anc[u]]++,(ans*=1+(siz[u]>1))%=mod;
	for(int i=0;i<m;i++){
		if(!vec[i])continue;
		int son=P[anc[i]],sum=son+M[anc[i]];
		if(son)(ans*=Fac[sum]*IFac[sum-son]%mod)%=mod,P[anc[i]]=0;
	}
	printf("%lld\n",ans);
}
signed main(){
	for(int i=Fac[0]=1;i<=100000;i++)Fac[i]=Fac[i-1]*i%mod;
	IFac[100000]=qpow(Fac[100000],mod-2);
	for(int i=99999;~i;i--)IFac[i]=IFac[i+1]*(i+1)%mod;
	int T=read();while(T--)solve();
	return 0;
}
// an interesting fact : different a_i wouldn't be in the same seg

posted @ 2023-08-23 08:51  pidan007  阅读(27)  评论(0编辑  收藏  举报