• UOJ 348 【WC2018】州区划分——子集卷积


    题目:http://uoj.ac/problem/348

    参考:https://www.cnblogs.com/NaVi-Awson/p/9242645.html#%E5%AD%90%E9%9B%86%E5%8D%B7%E7%A7%AF

    FMT就是快速莫比乌斯变换/反演,解决或卷积的问题,和 FWT 时间复杂度一样。

    FWT定义了 ( a'[i]=sumlimits_{j|i=i}a[j] ) ,利用倍增算出 a'[ ] 作为点值,相乘之后再算回去; FMT 也定义了这样的东西,但计算 a'[ ] 的方法是高维前缀和。

    高维前缀和大概就是一维一维独立做前缀和。把每个二进制位看成取值只有 0 , 1 的一个维度,这样的前缀和就是子集的和;所以这一位是 1 的时候就累计上其他位和自己一样、这一位是 0 的那些数的答案;可以想到每个子集会在枚举到最高的与自己不同的位(如果是从低位到高位枚举的)的时候被计入。

    FMT 从 a'[ ] 变回 a[ ] 的方法就是把刚才加的那些位置都减回去。如果加的时候是从低位到高位枚举,此时应该从高位到低位枚举;但仍从低位到高位枚举也没问题,大概和 FWT 那里的想法一样。

    修改一下 FMT ,就能求子集卷积了。

    子集卷积与或卷积的不同在于或起来的那两个数不能有交。

    这个限制就通过使那两个数的 1 的个数加起来正好等于自己的 1 的个数来解决。

    a[ i ][ S ]表示 1 的个数为 i 、S 的答案;如果 S 的 1 的个数不为 i ,这个值就是 0 ; a'[ i ][ S ] 表示 1 的个数为 i 、S 的所有子集的答案。有 ( c'[i][S]=sumlimits_{j=0}^{i}a'[j][S]*b'[i-j][S] ) 。

    从 a[ i ][ S ] 到 a'[ i ][ S ] 就是对于这个 i 做一遍高维前缀和。从 a'[ i ][ S ] 到 a[ i ][ S ] 就是把高维前缀和倒过来做一遍。

    这道题的式子是 ( dp[i][S]*w^p[i][S] = sumlimits_{j=0 , Tsubseteq S}^{i-1}dp[j][T]*w^p[i-j][S-T] ) ,好在算 dp[ i ] 的时候用到的 dp[ j ] 的 j<i ,所以把 i 放在最外层枚举即可。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define ll long long
    using namespace std;
    int rdn()
    {
      int ret=0;bool fx=1;char ch=getchar();
      while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
      while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
      return fx?ret:-ret;
    }
    const int N=25,M=425,K=(1<<21)+5,mod=998244353;
    int n,m,p,hd[N],xnt,to[M],nxt[M];
    int w[N][K],w2[K],dp[N][K],bin[N],ct[K];
    bool ok[K],vis[N];
    void upd(int &x){x>=mod?x-=mod:0;}
    int pw(int x,int k)
    {int ret=1;while(k){if(k&1)ret=(ll)ret*x%mod;x=(ll)x*x%mod;k>>=1;}return ret;}
    void add(int x,int y){to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;}
    void dfs(int cr,int fa,int s)
    {
      vis[cr]=1;
      for(int i=hd[cr],v;i;i=nxt[i])
        if(!vis[v=to[i]]&&(s&bin[v-1]))dfs(v,cr,s);
    }
    bool chk(int s)
    {
      for(int i=1;i<=n;i++)
        {
          if(!(s&bin[i-1]))continue; bool deg=0;vis[i]=0;
          for(int j=hd[i];j;j=nxt[j])
        if(s&bin[to[j]-1])deg^=1;
          if(deg)return true;
        }
      int i;
      for(i=1;i<=n;i++)if(s&bin[i-1]){dfs(i,0,s);break;}
      for(i++;i<=n;i++)if((s&bin[i-1])&&!vis[i])return true;
      return false;
    }
    void init()
    {
      bin[0]=1;for(int i=1;i<=n;i++)bin[i]=bin[i-1]<<1;
      for(int s=1;s<bin[n];s++)ct[s]=ct[s-(s&-s)]+1;///s from 1
      for(int s=0;s<bin[n];s++)ok[s]=chk(s);
    }
    void fmt(int *a,bool fx)
    {
      for(int i=0;i<n;i++)
        for(int s=0;s<bin[n];s++)
          if(s&bin[i])
        (fx?a[s]+=mod-a[s^bin[i]]:a[s]+=a[s^bin[i]]),upd(a[s]);
    }
    int main()
    {
      n=rdn();m=rdn();p=rdn();
      for(int i=1,u,v;i<=m;i++)
        u=rdn(),v=rdn(),add(u,v),add(v,u);
      init();
      for(int i=1;i<=n;i++)w[1][bin[i-1]]=rdn();
      for(int s=1;s<bin[n];s++)w[ct[s]][s]=w[ct[s]-1][s-(s&-s)]+w[1][s&-s];
      for(int s=0;s<bin[n];s++)w[ct[s]][s]=pw(w[ct[s]][s],p);
      for(int s=0;s<bin[n];s++)w2[s]=pw(w[ct[s]][s],mod-2);
      for(int s=0;s<bin[n];s++)if(!ok[s])w[ct[s]][s]=0;//w2 is alright
      dp[0][0]=1;
      for(int i=1;i<=n;i++)
        {
          fmt(dp[i-1],0);  fmt(w[i],0);//i-j may =i
          for(int j=0;j<i;j++)
        for(int s=0;s<bin[n];s++)
          dp[i][s]=(dp[i][s]+(ll)dp[j][s]*w[i-j][s])%mod;
          fmt(dp[i],1);
          for(int s=0;s<bin[n];s++)
        if(ct[s]==i)dp[i][s]=(ll)dp[i][s]*w2[s]%mod;
        else dp[i][s]=0;
        }
      printf("%d
    ",dp[n][bin[n]-1]);
      return 0;
    }
  • 相关阅读:
    自旋锁和互斥锁区别
    NFS挂载Android文件系统
    查找文件命令
    make menuconfig出错解决方法
    专访Linux嵌入式开发韦东山操作系统图书作者--转
    Ubuntu进不入系统,一直停留在ubuntu图标画面(转)
    查看驱动打印信息
    aiku给你们最真心地学习建议--转
    SZ,RZ传送文件
    在Android下运行Linux平台编译的程序
  • 原文地址:https://www.cnblogs.com/Narh/p/10258865.html
Copyright © 2020-2023  润新知