1. 程式人生 > >LOJ6356 四色燈(容斥+dp

LOJ6356 四色燈(容斥+dp

題目連結

紀念第一次所有的解析全寫在程式碼裡面
QWQ

這裡就簡單說幾句了

首先一個燈有貢獻,當且僅當他被按了 4 k 4k 次。

那麼我們定義 f ( S

) f(S) 表示 [ 1 , n ] [1,n]
中有多少個數 x x 是集合 S S 中元素的公倍數
f (
S ) = n l c m x S x f(S) = \frac{n}{lcm_{x\in S} x}

這裡需要注意的是,求 l c m lcm 的時候,要兩兩合併,不能用整體的乘積除以 g c d gcd

但是很容易發現,要是這樣計算的,會有重複的情況別包含進去,就比如說較小的集合公倍數,一定會包含它超集的公倍數,所以的話,我們定義
g ( S ) g(S) 表示 [ 1 , n ] [1,n] 中有多少個數 x x 是集合 S S 的公倍數,且不存在更大的集合 T T 使得 x x T T 中元素的公倍數

可以通過容斥在 O ( 3 m ) O(3^m) 內計算出來,大概就是對於一個集合 S S ,你去列舉他所有的超集,然後減去那些可能會重複的(原理和正解的類似,都寫在程式碼裡面了)

那麼 a n s = g ( S ) k C l e n g t h ( s ) 4 k × 2 m l e n g t h ( s ) ans = \sum g(S) * \sum_k C_{length(s)}^{4k}\times 2^{m-length(s)}

這裡的原理的,底下的程式碼裡有寫

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;
}