1. 程式人生 > >UOJ 348 【WC2018】州區劃分——子集卷積

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]=\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; }