LOJ6356 四色灯(容斥+dp

纪念第一次所有的解析全写在代码里面
QWQ

这里就简单说几句了

首先一个灯有贡献,当且仅当他被按了\(4k\)次。

那么我们定义\(f(S)\)表示\([1,n]\)中有多少个数\(x\)是集合\(S\)中元素的公倍数

\[f(S) = \frac{n}{lcm_{x\in S} x} \]

这里需要注意的是,求\(lcm\)的时候,要两两合并,不能用整体的乘积除以\(gcd\)

但是很容易发现,要是这样计算的,会有重复的情况别包含进去,就比如说较小的集合公倍数,一定会包含它超集的公倍数,所以的话,我们定义
\(g(S)\)表示\([1,n]\)中有多少个数\(x\)是集合\(S\)的公倍数,且不存在更大的集合\(T\)使得\(x\)\(T\)中元素的公倍数

可以通过容斥在\(O(3^m)\)内计算出来,大概就是对于一个集合\(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;
}

posted @ 2018-12-22 16:25  y_immortal  阅读(208)  评论(0编辑  收藏  举报