P4424 [HNOI/AHOI2018]寻宝游戏 思维题

题意:

戳这里

分析:

  • 暴力 \(O(qn\times 2^m)\)

  • 正解:

    stO 考场上直接切掉这道题的巨佬们,蒟蒻瑟瑟发抖

    我们按照操作和数字的组合分清况来讨论:

    \(1 \& 0\to 0\) \(1|0\to 1\) \(1 \& 1\to 1\) \(1|1\to 1\)

    \(0 \& 0\to 0\) \(0|0\to 0\) \(0\&1\to 0\) \(0|1\to 1\)

    我们观察发现第二列和第三列有一些特殊性质:

    \(|0\)\(\&1\) 的组合不会对原来的值产生任何影响 而 \(|1\) 相当于赋值为 \(1\)\(\& 0\) 相当于赋值为 \(0\)


关键的一步来了,我们对操作进行转化,因为操作只有 \(\&\ |\) 两种,和 \(0,1\) 一样,那么我们考虑将操作也转化成 \(0,1\) 序列, 我们把 \(|\) 看做 \(0\) , $& $ 看成 \(1\)

这样一来,我们就能通过操作的大小关系来判断出最后每一位的结果,我们按位 考虑,对于每一位从 \(n\)\(1\) 看成一个从高到低的二进制数字序列,从 \(n\)\(1\) 的操作看成从高到低的二进制操作序列,如果操作序列和数字序列某一位相同,那么没有任何影响,如果操作序列某一位小于数字序列,相当于赋值为 \(1\) ,如果操作序列某一位大于数字序列,相当于赋值为 \(0\) 由于后来的操作会覆盖开始的操作,所以高位的答案会覆盖低位的答案,这和二进制下的大小一样,高位操作优先,所以我们发现,当

  1. 操作序列小于数字序列,这一位为 \(1\) ( 不能是等于数字序列,是因为最开始这一位为 \(0\) ,必须要有 \(|1\) 操作)
  2. 操作序列大于等于数字序列,这一位为 \(0\)

然后我们对于原序列每一位,建出数字序列,然后根据查询串的 \(0,1\) 关系,判断出操作序列的上界和下界,之间的每一种操作都可以取到

代码:

#include<bits/stdc++.h>
#define pii pair<int,int>
#define mk(x,y) make_pair(x,y)
#define lc rt<<1
#define rc rt<<1|1
#define pb push_back
#define fir first
#define sec second
#define inl inline
#define reg register

using namespace std;

namespace zzc
{
	inline int read()
	{
		int x=0,f=1;char ch=getchar();
		while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
		while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
		return x*f;
	}
	
	const int maxn = 5005;
	const int mod = 1e9+7;
	int n,m,q;
	string s[maxn],r;
	int rk[maxn],seq[maxn];
	char ch;
	
	bool cmp(int x,int y)
	{
		return s[x]<s[y];
	}
	
	long long calc(string &a,string &b)
	{
		long long bas=1,res=0;
		for(int i=n-1;i>=0;i--)
		{
			res=(res+bas*(a[i]+mod-b[i])%mod)%mod;
			bas=bas*2%mod;
		}
		return res;
	}
	
	void work()
	{
		n=read();m=read();q=read();
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=m;j++)
			{
				ch=getchar();
				while(ch!='0'&&ch!='1') ch=getchar();
				s[j]+=ch;
			}
		}
		for(int i=1;i<=m;i++) reverse(s[i].begin(),s[i].end());
		for(int i=1;i<=n;i++) s[0]+='0',s[m+1]+='1';
		for(int i=1;i<=m+1;i++) seq[i]=i;
		sort(seq+1,seq+m+1,cmp);
		for(int i=1;i<=m+1;i++) rk[seq[i]]=i;
		while(q--)
		{
			cin>>r;
			int dwn=0,upn=m+1;
			for(int i=1;i<=m;i++)
			{
				if(r[i-1]=='0') dwn=max(dwn,rk[i]);
				else upn=min(upn,rk[i]);
			}
			if(dwn>upn) puts("0");
			else printf("%lld\n",(1ll*(upn==m+1)?1:0)+calc(s[seq[upn]],s[seq[dwn]]));
		}
	}

}

int main()
{
	zzc::work();
	return 0;
}

posted @ 2021-01-15 15:33  youth518  阅读(80)  评论(0编辑  收藏  举报