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;
}
原文地址:https://www.cnblogs.com/Narh/p/10258865.html