zoukankan      html  css  js  c++  java
  • LOJ6356 四色灯(容斥+dp

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

    这里就简单说几句了

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

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

    [f(S) = frac{n}{lcm_{xin 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} imes 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;
    }
    
    
  • 相关阅读:
    快乐的一天从AC开始 | 20210717 | 牛客小白月赛36J
    快乐的一天从AC开始 | 20210717 | P4839
    P7295-[USACO21JAN]Paint by Letters P【平面图欧拉公式】
    泛型
    List集合
    红黑树被定义
    单例模式的双重检查锁模式为什么必须加 volatile?
    什么是 happens-before 规则?
    解决AtomicInteger 在高并发下性能问题
    什么是指令重排序?为什么要重排序?
  • 原文地址:https://www.cnblogs.com/yimmortal/p/10161518.html
Copyright © 2011-2022 走看看