[JOI 2018 Final]毒蛇越獄

一、題目

點此看題

二、解法

考慮一個子集反演:
f(s)=isg(i)f(s)=\sum_{i\in s} g(i)g(s)=is(1)sif(i)g(s)=\sum_{i\in s}(-1)^{|s|-|i|}f(i)下面給出證明:
f(s)=isji(1)ijf(j)f(s)=\sum_{i\in s}\sum_{j\in i}(-1)^{|i|-|j|}f(j)f(s)=jsf(j)i=0sj(1)iC(sj,i)f(s)=\sum_{j\in s}f(j)\sum_{i=0}^{|s|-|j|}(-1)^iC(|s|-|j|,i)f(s)=jsf(j)(11)sj=f(s)f(s)=\sum_{j\in s}f(j)(1-1)^{|s|-|j|}=f(s)對於本題,因爲min(0,1,?)6\min(|0|,|1|,|?|)\leq 6,我們分情況討論:

  • 最小值是?|?|時,直接暴力枚舉。
  • 最小值是1|1|時,就用子集反演,時間複雜度O(26)O(2^6)
  • 最小值是0|0|時,就把00變成1111變成00,初始數組還是一樣,??還是不變,就用1|1|的方法。
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 1<<20;
int read()
{
    int num=0,flag=1;char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
    while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
    return num*flag;
}
int n,m,lim,a[M],b[M],c[M],bit[M];char s[M];
void fwt(int *a,int n,int op)
{
	for(int i=1;i<n;i<<=1)
		for(int p=i<<1,j=0;j<n;j+=p)
			for(int k=0;k<i;k++)
			{
				if(op==1) a[i+j+k]=a[i+j+k]+a[j+k];
				else a[i+j+k]=a[i+j+k]-a[j+k];
			}
}
signed main()
{
	n=read();m=read();
	scanf("%s",s);lim=1<<n;
	for(int i=0;i<lim;i++)
	{
		a[i]=b[(lim-1)^i]=c[i]=s[i]-'0';
		bit[i]=bit[i>>1]+(i&1);
	}
	fwt(a,lim,1);fwt(b,lim,1);
	while(m--)
	{
		scanf("%s",s);
		int c0=0,c1=0,cw=0,s0=0,s1=0,sw=0,ans=0;
		for(int i=0;i<n;i++)
		{
			if(s[i]=='0') c0++,s0|=(1<<(n-i-1));
			if(s[i]=='1') c1++,s1|=(1<<(n-i-1));
			if(s[i]=='?') cw++,sw|=(1<<(n-i-1));
		}
		int mn=min(c0,min(c1,cw));
		if(mn==cw)
		{
			for(int i=sw;;i=(i-1)&sw)
			{
				ans+=c[i^s1];
				if(i==0) break;
			}
		}
		else if(mn==c1)
		{
			for(int i=s1;;i=(i-1)&s1)
			{
				if(bit[s1^i]&1) ans-=a[i|sw];
				else ans+=a[i|sw];
				if(i==0) break;
			}
		}
		else if(mn==c0)
		{
			for(int i=s0;;i=(i-1)&s0)
			{
				if(bit[s0^i]&1) ans-=b[i|sw];
				else ans+=b[i|sw];
				if(i==0) break;
			}
		}
		printf("%d\n",ans);
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章