「JOI2018」Snake Escaping 題解
阿新 • 來源:網路 • 發佈:2022-05-05
思維定勢屬於是,怎麼又被 \(2^L,Q\) 之類的東西搞亂了啊。
首先根據鴿巢原理,一個詢問中出現次數最小的字元出現次數最多是 \(\lfloor \frac{L}{3} \rfloor\) 嘛,然後分類考慮:
- 如果這個字元是
?:那很好辦,暴力列舉?分別代表什麼就好了; - 否則,
- 如果這個字元是 \(0\):我們將 \(0\) 看作這一位是
?的答案減去這一位是 \(1\) 的答案; - 如果這個字元是 \(1\):跟上面的差不多。
- 如果這個字元是 \(0\):我們將 \(0\) 看作這一位是
現在構建出一個大致的框架,我們思考出現次數最少的字元是 \(0\) 怎麼做。
考慮容斥,答案就是沒有 \(0\) 轉成 \(1\) 的方案數,減去有一個 \(0\) 轉成 \(1\) 的方案數,加上有兩個 \(0\) 轉成 \(1\) 的方案數……容易發現列舉哪些 \(0\) 變成了 \(1\) 的時間複雜度是 \(O(2^{p})\)(其中 \(p\) 為 \(0\) 的個數)。
然後現在的問題是,我們確定了一些位置是 \(1\),另外一些位置可以隨便填的答案。記確定的位置的狀態為 \(S\),我們要求的就是編號為 \(S\) 的超集的蛇毒性和。高維字首和就可以做到 \(O(1) \times O(2^{p}) = O(2^{p})\)。
最後是出現次數最少的字元是 \(1\) 怎麼做。沿用上面的方法變成一個求子集和的問題,也可以用高維字首和來處理。
具體實現可以採用 FMT/FWT 實現,程式碼裡用的是 FWT。
/*
他決定要“格”院子裡的竹子。於是他搬了一條凳子坐在院子裡,面對著竹子硬想了七天,結果因為頭痛而宣告失敗。
DONT NEVER AROUND . //
*/
#include<bits/stdc++.h>
using namespace std;
int l,q;
void FWT_or(int *f){for(int o=2,k=1;o<=(1<<l);o<<=1,k<<=1) for(int i=0;i<(1<<l);i+=o) for(int j=0;j<k;++j) f[i+j+k]+=f[i+j];}
void FWT_and(int *f){for(int o=2,k=1;o<=(1<<l);o<<=1,k<<=1) for(int i=0;i<(1<<l);i+=o) for(int j=0;j<k;++j) f[i+j]+=f[i+j+k];}
int f[(1<<20)+5],g[(1<<20)+5],a[(1<<20)+5];
char s[(1<<20)+5],snk[25];
int cq[25],c0[25],c1[25],pq,p0,p1;
int dfs(int S,int p)
{
if(p==pq) return a[S];
return dfs(S,p+1)+dfs(S|(1<<cq[p]),p+1);
}
int dfs0(int S,int p)
{
if(p==p0) return g[S];
return dfs0(S,p+1)-dfs0(S|(1<<c0[p]),p+1);
}
int dfs1(int S,int p)
{
if(p==p1) return f[S];
return dfs1(S|(1<<c1[p]),p+1)-dfs1(S,p+1);
}
int main(){
scanf("%d %d",&l,&q);
scanf("%s",s);
for(int i=0;i<(1<<l);++i) f[i]=g[i]=a[i]=(s[i]-'0');
FWT_or(f),FWT_and(g);
while(q-->0)
{
scanf("%s",snk);
reverse(snk,snk+l);
pq=p0=p1=0;
int S0=0,S1=0;
for(int i=0;i<l;++i)
{
if(snk[i]=='?') cq[pq++]=i,S0|=(1<<i);
if(snk[i]=='0') c0[p0++]=i;
if(snk[i]=='1') c1[p1++]=i,S1|=(1<<i);
}
int st=min({pq,p0,p1}),ans;
if(st==pq) ans=dfs(S1,0);
else if(st==p0) ans=dfs0(S1,0);
else ans=dfs1(S0,0);
printf("%d\n",ans);
}
return 0;
}
