给出(n+1)个长度为(m)的二进制数组成的序列(规定(a_0=0),其它读入),每个询问给出一个二进制数。你需要求出在这个序列中每个相邻位置之间添加符号(and)或(or)(规定运算从左往右进行),使得运算结果为询问的二进制数的方案数。
(n,qle 1000,mle 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_1Leftrightarrow (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
",l<r?(v[r]-(l>=0?v[l]:0)+mo)%mo:0);
}
return 0;
}