UOJ 348 【WC2018】州区划分——子集卷积
参考: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]=\sum\limits_{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]=\sum\limits_{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] = \sum\limits_{j=0 , T\subseteq 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\n",dp[n][bin[n]-1]); return 0; }