abc349F 題解

g1ove發表於2024-04-13

我都自閉了,賽後一分鐘調出來。

題意

給定一個長度為 \(n\) 的序列,現在從原序列中取一個子序列,使得子序列中元素的最小公倍數為 \(m\)。求有多少個子序列滿足條件,\(\mod 998244353\)

\(1\le n\le 2\times 10^5\)

\(1\le A_i,m\le 10^{16}\)

思路

一個很明顯的性質就是,如果先將 \(m\) 分解質因數成 \(\prod p_k^{c_k}\) 的形式,子序列 \(U\) 中的數也分解質因數,那麼明顯,對於一個子序列成為答案的充要條件是:

  • 對於 \(\forall p_k\) ,有 \(\max\limits_{v\in U} c_v=c_k\)
  • \(m\) 的質因數集合等於子序列所有數子集的並集

通俗的說,就是對於 \(m\) 的每個質因數,能在子序列找到一個數,使它們的最高次冪相同。

進行一些適當的預處理,排除掉不可能貢獻的數。
這個時候,問題轉化為:

  • 有若干個序列,從中選取若干個序列,使得對於 \(\forall i,\max a_i=b_i\)

舉個例子:
不妨令 \(m=24\) ,此時 \(m=2^3\times 3\)
那麼,\(6=2\times 3\)\(12=2^2\times 3\)\(8=2^3\)

那麼原問題化為:給定 \(a=\{3,1\}\)\(3\) 個序列 \(\{1,1\},\{2,1\},\{3,0\}\) ,很明顯只有 \(3\) 種方案滿足要求。

那麼我們知道,只有 \(a_i=b_i\),才會產生合法的貢獻。我們計如果第 \(i\) 個數第 \(j\) 位滿足這個條件,那麼 \(d_{i,j}=1\),否則為 \(0\)

現在,問題轉化成一個通俗模型:

  • 給定 \(n\) 個長度為 \(k\)\(01\) 串,求它們或起來全為 \(1\) 的方案數。

很明顯有個 \(O(n2^k)\) 做法,然後被出題人卡掉了。

蒟蒻不會高階演算法,只會樸素容斥。

思路:正難則反,欽定其中某些位數為 \(0\),求方案。
這個很好做,把這些位數全為 \(0\) 的數個數求出來 為\(cnt\),然後貢獻就是 \(2^{cnt}\)

然後計算過程發現就是求交集而已,直接上一個 bitset 暴力最佳化即可。然後就過了。時間複雜度 \(O(\frac{n2^k}{w})\)

code

#include<bits/stdc++.h>
#define N 200005
#define ll long long
using namespace std;
const int mod=998244353;
int n;
ll m,p[N];
int t[N],tot;
int len;
bitset<N> g[16],f;
ll pw[N],ans;
int main()
{
	scanf("%d%lld",&n,&m);
	for(ll i=2;i<=sqrt(m);i++)
	{
		if(m%i) continue;
		p[++tot]=i;
		while(!(m%i)) m/=i,t[tot]++;
	}
	if(m^1) p[++tot]=m,t[tot]=1;
	f[0]=1;
	for(int i=1;i<=n;i++)
	{
		int mask=0,tag=0;
		ll x;
		scanf("%lld",&x);
		for(int j=1;j<=tot;j++)
		{
			int v=0;
			while(!(x%p[j])) x/=p[j],v++;
			if(v>t[j]) 
			{
				tag=1;
				break;
			}
			if(v^t[j]) mask|=(1<<j-1);
		}
		if(x^1||tag) continue;
		++len;
		for(int j=0;j<tot;j++)
			if(mask&(1<<j)) g[j].set(len);
	}
	pw[0]=1;
	for(int i=1;i<=len;i++) pw[i]=(pw[i-1]*2)%mod;
	ans=pw[len];
	for(int i=1;i<(1<<tot);i++)
	{
		int cnt=0;
		f.set();
		for(int j=0;j<tot;j++)
			if(i&(1<<j)) f&=g[j],cnt++;
		if(!(cnt&1)) ans=(ans+pw[f.count()])%mod;
		else ans=(ans+mod-pw[f.count()])%mod;
	}
	if(!tot) ans=(ans-1+mod)%mod;
	printf("%lld",ans);
	return 0;
}

提交記錄

跑的飛快,最慢的點 314ms