「JOI 2018 Final」毒蛇越狱

「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\) 的字符串,其中字符均在 09 的范围内,第 \(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;
}
上一篇:面向对象高级


下一篇:有效的括号字符串