zoukankan      html  css  js  c++  java
  • 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;
    }
  • 相关阅读:
    ExtJS小技巧
    Oracle 表的行数、表占用空间大小,列的非空行数、列占用空间大小 查询
    NPM 私服
    IDEA 不编译java以外的文件
    SQL 引号中的问号在PrepareStatement 中不被看作是占位符
    Chrome 浏览器自动填表呈现淡黄色解决
    批量删除Maven 仓库未下载成功.lastupdate 的文件
    Oracle 11g 监听很慢,由于监听日志文件太大引起的问题(Windows 下)
    Hibernate 自动更新表出错 建表或添加列,提示标识符无效
    Hibernate 自动更新表出错 More than one table found in namespace
  • 原文地址:https://www.cnblogs.com/Narh/p/10258865.html
Copyright © 2011-2022 走看看