1. 程式人生 > >其它 >「JOI2018」Snake Escaping 題解

「JOI2018」Snake Escaping 題解


思維定勢屬於是,怎麼又被 \(2^L,Q\) 之類的東西搞亂了啊。

首先根據鴿巢原理,一個詢問中出現次數最小的字元出現次數最多是 \(\lfloor \frac{L}{3} \rfloor\) 嘛,然後分類考慮:

  • 如果這個字元是 ?:那很好辦,暴力列舉 ? 分別代表什麼就好了;
  • 否則,
    • 如果這個字元是 \(0\):我們將 \(0\) 看作這一位是 ? 的答案減去這一位是 \(1\) 的答案;
    • 如果這個字元是 \(1\):跟上面的差不多。

現在構建出一個大致的框架,我們思考出現次數最少的字元是 \(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;
}