LOJ6356 四色燈(容斥+dp
阿新 • • 發佈:2018-12-04
紀念第一次所有的解析全寫在程式碼裡面
QWQ
這裡就簡單說幾句了
首先一個燈有貢獻,當且僅當他被按了 次。
那麼我們定義
表示
中有多少個數
是集合
中元素的公倍數
這裡需要注意的是,求 的時候,要兩兩合併,不能用整體的乘積除以
但是很容易發現,要是這樣計算的,會有重複的情況別包含進去,就比如說較小的集合公倍數,一定會包含它超集的公倍數,所以的話,我們定義
表示
中有多少個數
是集合
的公倍數,且不存在更大的集合
使得
是
中元素的公倍數
可以通過容斥在 內計算出來,大概就是對於一個集合 ,你去列舉他所有的超集,然後減去那些可能會重複的(原理和正解的類似,都寫在程式碼裡面了)
那麼
這裡的原理的,底下的程式碼裡有寫
QWQ
但是我們發現這個東西時間複雜度是跑不過,那麼我們就需要一些其他角度的計算方式或者狀態
QWQ由於我比較懶,直接搬dalao的部落格了
另外我的很多想法都直接寫在程式碼裡面QWQ
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define mk makr_pair
#define ll long long
#define int long long
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
const int maxn = 1010;
const int mod = 998244353;
int c[maxn][maxn];
int n,m;
int a[maxn];
int ans;
int f[maxn],g[maxn];
void init()
{
for (int i=0;i<=1000;i++) c[i][i]=1,c[i][0]=1;
for (int i=2;i<=1000;i++)
{
for (int j=1;j<i;j++)
c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
}
}
int gcd(int a,int b)
{
if (b==0) return a;
else return gcd(b,a%b);
}
ll qsm(ll i,ll j)
{
ll ans=1;
while (j)
{
if (j&1) ans=ans*i%mod;
i=i*i%mod;
j>>=1;
}
return ans;
}
signed main()
{
init();
n=read(),m=read();
for (int i=1;i<=m;i++) a[i]=read();
for (int i=0;i<(1 << m);i++)
{
int lcm = 1;
for (int j=1;j<=m;j++)
{
if ((1 << (j-1))&i)
{
lcm = lcm * a[j] / gcd(a[j],lcm); //兩兩lcm合併
if (lcm>n) break;
}
}
if (lcm>n) continue;
int ymh = __builtin_popcount(i);
//定義a[S]表示在[1,n]中,有多少個數是S集合的公倍數
f[ymh]+=n/lcm; //f[i]則表示所有長度為i的S的sigma(a[S])
f[ymh]%=mod;
}
//定義b(S)表示[1,n]中有多少個數x是集合S的公倍數,且不存在更大的集合T使得x是T中元素的公倍數;
//那麼g(i)就表示對應長度i的集合的sigma(b[S])
for (int i=0;i<=m;i++) g[i]=f[i];
//因為考慮到一個長度的集合,我們可以合併到一起去算
//最後ans用g陣列來算,就不會出現出現重複的情況了
for (int i=m;i>=0;i--)
for (int j=i+1;j<=m;j++)
g[i]=(g[i]-g[j]*c[j][i]%mod+mod)%mod; //這裡可以理解為,就是每一個長度為j(j>i)的集合 ,都 為i的集合,而這些集合的公倍數,每一個都會在長度更小的集合中重複算一次,所以就減去QWQ了
//也就是說,對於長度為j的每一個b(S)中的數,都會在長度為i的他的子集中的對應的a(S)中出現,但是這個是不合法的,所以我們要減去這個貢獻
for (int i=0;i<=m;i++) ans=(ans+qsm(2,m-i)*g[i]%mod*(c[i][0]+c[i][4]+c[i][8]+c[i][12]+c[i][16]+c[i][20])%mod)%mod;
//最後一行轉移的式子是我們考慮列舉這個長度,然後只要選出來4k個,就一定是合法的(可以理解為g[i]中的數,在小的集合的貢獻(這裡子啊之前並不會算過,具體可以看g和b的定義),然後剩下的是隨便選,因為我們考慮的是當前長度的貢獻,
ans=ans%mod*qsm(qsm(2,m),mod-2)%mod;
cout<<ans;
return 0;
}