把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷4424】[HNOI/AHOI2018] 寻宝游戏(位运算思维题)

点此看题面

大致题意: 给你\(n\)\(m\)位二进制数。每组询问给你一个\(m\)位二进制数,要求你从\(0\)开始,依次对于这\(n\)个数进行\(and\)\(or\)操作,问有多少种方案能够得到给你的这个二进制数。

找规律

不难想到去对每一位分别讨论。

则根据位运算法则可得:

  • 当你把某一位的数\(and\ 0\),就相当于把这一位数赋值为\(0\)
  • 当你把某一位的数\(or\ 1\),就相当于把这一位数赋值为\(1\)
  • 当你把某一位的数\(and\ 1\)或者\(or\ 0\)时,这一位的值均不变。

则可以得出一个结论:

  • 若给定数这一位为\(0\),则对于这一位的运算中最后一次\(and\ 0\)要出现在最后一次\(or\ 1\)之后(或者两者都未出现)。
  • 若给定数这一位为\(1\),则对于这一位的运算中最后一次\(or\ 1\)要出现在最后一次\(and\ 0\)之后(注意\(or\ 1\)必须有,\(and\ 0\)可有可无)。

但这样依然不太好操作,所以我们要进行进一步转化。

转化

考虑把操作序列转化为一个二进制数,\(or\)\(0\)\(and\)\(1\),且较后操作处于较高位。

然后把题目中给出的\(n\)个长度为\(m\)的数变为\(m\)个长度为\(n\)的数,第\(i\)个数由初始的\(n\)个数的第\(i\)位组成,且编号较大的数处于较高位。

这样一来操作与数就可以一一对应了。

然后考虑如果对应位相等,表示该操作无影响。

而不相等那一位,若操作序列中的值为\(0\),数中的值为\(1\),即操作序列中这一位小于该数中的这一位,说明是赋值为\(1\),反之是赋值为\(0\)

由于二进制下比大小看不相等的最高位,所以我们可以得出结论:

  • 若操作序列所表示的二进制数小于该数,则最终结果中该数所对应位上为\(1\)(不能等于是因为一定要有\(or\ 1\)操作)。
  • 若操作序列所表示的二进制数大于等于该数,则最终结果中该数所对应位上为\(0\)

也就是说,一个符合条件的操作序列所表示的二进制数,要满足其小于所有应得位为\(1\)的数,大于等于所有应得位为\(0\)的数。

而这也就是要小于所有应得位为\(1\)的数的最小值,大于等于所有应得数为\(0\)的数的最大值。

则可以先将所有数排序,然后求出所有数取模后的值,然后对于询问从小到大找最大值,从大到小找最小值即可。

具体实现详见代码。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1000
#define M 5000
#define X 1000000007
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,m,v[M+5],pw[M+5];
struct data
{
	int p;string s;
	I bool operator < (Con data& o) Con {return s<o.s;}
}s[M+5];
I int XSub(CI x,CI y) {return x<y?x-y+X:x-y;}
int main()
{
	RI Qt,i,j,l,r;string st;for(scanf("%d%d%d",&n,&m,&Qt),i=1;i<=m;++i) s[i].p=i;//初始化
	for(i=1;i<=n;++i) for(cin>>st,j=1;j<=m;++j) s[j].s=st[j-1]+s[j].s;//初始化出字符串
	for(pw[0]=i=1;i<=n;++i) (pw[i]=pw[i-1]<<1)>=X&&(pw[i]-=X);v[m+1]=pw[n];//预处理2的幂
	for(sort(s+1,s+m+1),i=1;i<=m;++i) {for(j=0;j^n;++j) s[i].s[j]^48&&Inc(v[i],pw[n-j-1]);}//排序,预处理出每个数的值
	W(Qt--)
	{
		for(cin>>st,l=0,i=m;i;--i) if(st[s[i].p-1]^'1') {l=i;break;}//找最小值
		for(r=m+1,i=1;i<=m;++i) if(st[s[i].p-1]^'0') {r=i;break;}//找最大值
		printf("%d\n",l<=r?XSub(v[r],v[l]):0);//计算答案
	}return 0;
}
posted @ 2019-06-02 14:02  TheLostWeak  阅读(282)  评论(0编辑  收藏  举报