LOJ2494. 「AHOI / HNOI2018」寻宝游戏

给出\(n+1\)个长度为\(m\)的二进制数组成的序列(规定\(a_0=0\),其它读入),每个询问给出一个二进制数。你需要求出在这个序列中每个相邻位置之间添加符号\(\and\)\(\or\)(规定运算从左往右进行),使得运算结果为询问的二进制数的方案数。

\(n,q\le 1000,m\le 5000\)


没有看到运算顺序所以想了一整个白天甚至浪费了一节物理课

考虑运算加数字接在后面的意义:\(\and 0\)\(\or 1\)分别表示赋值,剩下两个分别表示保持不变。

于是有个normal的模型转化:从后往前扫,维护个结果\(res\),一开始每个位置未确定,每次用\(a_i\)中的所有\(0\)或所有\(1\)覆盖\(res\)未确定的位置,要求覆盖的这个位置一定符合询问的数。可以直接暴力做,只要加上每个位置都被覆盖之后就退出的剪枝,就可以做到\(O(\frac{nmq}{\omega})\)的复杂度,出题人没有卡,可以过。(考虑没有确定的位置,假如选了\(0\),设\(S_0\)表示结果应当\(0\)的位置集合,\(a_{i,0}\)表示\(a_i\)\(0\)的位置集合,如果\(a_{i,0}\subseteq S_0\)才可以递归下去;选\(1\)同理。进而发现\(a_{i,0}\subseteq S_0 \and a_{i,1}\subseteq S_1\Leftrightarrow (a_{i,0}=S_0)\),也就是只有\(a_{i,0}=S_0\)时会分叉。注意到分叉之后,没有确定的位置的目标值只有一种(假设为\(0\)),如果选\(1\)那么只有影响到的位置是空集才合法,此时如果选\(0\)就是覆盖全集会被剪枝掉)

正解很妙:

考虑搞出一个操作序列。把\(\and\)视作\(1\)\(\or\)视作\(0\)

\(n\)\(m\)位数转置变成\(m\)\(n\)位数。现在对于某个数\(b_i\),考虑它和\(opt\)的关系会如何决定结果。发现是从后往前,第一个不同的位置。假如这个位置上\(opt\)\(b_i\)分别为\(0,1\),那么最终结果是\(1\);如果是\(1,0\),或找不到不同的位置,结果是\(0\)

这其实就是比大小,当\(opt<b_i\)时为\(1\),否则为\(0\)

于是对\(b_i\)排序。每次根据询问的值计算出\(opt\)的范围即可。


using namespace std;
#include <bits/stdc++.h>
#define N 1005
#define M 5005
#define ull unsigned long long
#define mo 1000000007
int n,m,Q;
char str[M];
int _b[M][N],*b[M],p[M],v[M];
bool cmpb(int x[],int y[]){
	for (int i=n;i>=1;--i){
		if (x[i]<y[i]) return 1;
		if (x[i]>y[i]) return 0;
	}
	return 0;
}
int main(){
	freopen("in.txt","r",stdin);
	scanf("%d%d%d",&n,&m,&Q);
	for (int j=0;j<m;++j)
		b[j]=_b[j];
	for (int i=1;i<=n;++i){
		scanf("%s",str);
		for (int j=0;j<m;++j)
			b[j][i]=str[j]-'0';
	}
	sort(b,b+m,cmpb);
	for (int j=0;j<m;++j){
		int re=(b[j]-_b[0])/N;
		p[re]=j;
		for (int i=n;i>=1;--i)
			v[j]=(v[j]*2+b[j][i])%mo;
	}
	v[m+1]=1;
	for (int i=1;i<=n;++i)
		v[m+1]=v[m+1]*2%mo;
	while (Q--){
		scanf("%s",str);
		int l=-1,r=m+1;
		for (int j=0;j<m;++j)
			if (str[j]=='1')
				r=min(r,p[j]);
			else
				l=max(l,p[j]);
		printf("%d\n",l<r?(v[r]-(l>=0?v[l]:0)+mo)%mo:0);
	}
	return 0;
}
posted @ 2021-01-15 20:23  jz_597  阅读(79)  评论(0编辑  收藏  举报