「JOI2018」Snake Escaping 题解
思维定势属于是,怎么又被 \(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;
}