我都自閉了,賽後一分鐘調出來。
題意
給定一個長度為 \(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
。