「JOI 2018 Final」毒蛇越狱
题面:
JOI 研究所有 条毒蛇,这些毒蛇编号为 \(0,1,\dots,2^L-1\) 。每条毒蛇从头到尾被分成 \(L\) 段,每段的颜色为蓝、红中的一种。对于毒蛇 \(i\),令 \(i=\sum_{k=1}^{L}{c_k\times 2^{L-k}}.(0\le c_k\le 1)\) 为 \(i\) 的二进制展开,若 ,则毒蛇 的第 段是蓝色的,否则是红色的。
每条毒蛇有一个 \(0\) 到 \(9\) 的整数表示它的毒性。给出一个长度为 \(2^L\) 的字符串,其中字符均在 0
到 9
的范围内,第 \(i\) 位字符表示第 \(i-1\) 条毒蛇的毒性。
这些毒蛇移动速度非常快,所以他们经常从 JOI 研究所逃跑,因此,研究所附近的住户投诉时常看见从研究所逃出的毒蛇。
研究所整理出了 \(Q\) 天来住户的举报清单,第 \(i\) 天的收到的举报是一个长度为 \(L\) 且仅包含 0
, 1
, ?
的字符串 。
如果 \(T_d\) 的第 \(j\) 个字符为 0
,这意味着逃跑毒蛇的第 段是蓝色的;
如果 \(T_d\) 的第 \(j\) 个字符为 1
,这意味着逃跑毒蛇的第 段是红色的;
如果 \(T_d\) 的第 \(j\) 个字符为 ?
,这意味着没有人注意到逃跑毒蛇的第 段是什么颜色的。
研究所保证投诉均为准确的信息,并且根据这些信息,JOI 研究所每天会将逃跑的毒蛇全部捕获回来,因此会发生同一条毒蛇在不同日子逃跑的情况。
为了估计逃跑的毒蛇的风险,JOI 实验室的执行主任 K 教授想知道所有可能逃跑的毒蛇的毒性之和。你的任务是编写一个程序,给出 \(Q\) 天的投诉清单,计算每天可能从实验室逃跑的毒蛇的毒性之和。
注意本题空间限制较小。
首先考虑暴力,显然是枚举每一个问号是那一个数字,然后累计即可,那么这样的复杂度是 \(O(2^{cnt2})\),\(cnt2\) 表示问号的个数。
而实际上我们还有另外一种求法,给出的询问实际上可以看做一个高维空间,我们要做的就是统计这个空间中的权值和。那么我们可以用高维前缀和,然后通过容斥得到答案,这样的复杂度是 \(O(2^{cnt1})\) 的,\(cnt1\) 表示 \(1\) 的个数。
同样的,我们可以将数字取反,然后通过和上面类似的方法,把 \(0\) 当做 \(1\) 做一遍,这样的复杂度是 \(O(2^{cnt0})\) ,\(cnt0\) 表示 \(0\) 的个数。
而实际上,根据鸽巢定理我们知道 \(cnt0,cnt1,cnt2\) 最小的不会大于 \(6\)。所以我们取最小的那个并且用它对应复杂度的方法做就好了。
总时间复杂度 \(O(2^n\times n+2^6\times Q)\)。
代码如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN =(1<<20)+5;
bool Sunny;
int L,Q,f[MAXN],A[MAXN],B,C[MAXN],D,g[MAXN];
char S[MAXN],T[25];
bool Small;
ll Ans;
ll solve()
{
int cnt1=0,cnt0=0,cnt2=0;
int B=0,D=0,num=0,E=0;
for(int i=0;i<L;++i)
{
num<<=1;B<<=1;D<<=1;E<<=1;
if(T[i]=='1') ++cnt1,B|=1;
if(T[i]=='?') ++cnt2,D|=1;
if(T[i]=='0') ++cnt0,E|=1;
}
Ans=0;
if(cnt2<=6)
{
for(int i=0;i<L;++i) num=(num<<1)|(T[i]=='?'||T[i]=='1');
Ans+=A[num];
for(int i=D;i;i=(i-1)&D)
Ans+=A[num^i];
}
else if(cnt1<=6)
{
for(int i=0;i<L;++i) num=(num<<1)|(T[i]=='1'||T[i]=='?');
Ans=f[num];
for(int i=B;i;i=(i-1)&B)
{
if(C[i]%2==0) Ans+=f[num^i];
else Ans-=f[num^i];
}
}
else if(cnt0<=6)
{
for(int i=0;i<L;++i) num=(num<<1)|(T[i]=='1');
Ans=g[num];
for(int i=E;i;i=(i-1)&E)
{
if(C[i]%2==0) Ans+=g[num^i];
else Ans-=g[num^i];
}
}
return Ans;
}
int main()
{
// cout<<1.0*(&Small-&Sunny)/1024/1024<<"MB"<<endl;
freopen("d.in","r",stdin);
freopen("d.out","w",stdout);
scanf("%d %d",&L,&Q);
scanf("%s",S);
for(int i=0;i<(1<<L);++i) C[i]=__builtin_popcount(i);
for(int i=0;i<(1<<L);++i) f[i]+=S[i]-'0',A[i]=S[i]-'0',g[i]+=S[i]-'0';
for(int i=0;i<L;++i)
for(int j=0;j<(1<<L);++j) if((1<<i)&j) f[j]+=f[j^(1<<i)];
for(int i=0;i<L;++i)
for(int j=(1<<L)-1;j>=0;--j) if(((1<<i)&j)==0) g[j]+=g[j^(1<<i)];
while(Q--)
{
scanf("%s",T);
printf("%lld\n",solve());
}
return 0;
}