题目:https://gmoj.net/senior/#main/show/6856
题解:这道题是个智慧+乘法原理。
比赛的时候想到了一半正解,为什么说是一半呢,因为我想多了,而且没想到怎么打,于是只拿了45分。
正解:
找到0~(k-1)中有多少位第i位是可以位1的,假如是x,那么最后答案是2^x-n;
为什么呢?
首先如果有x个位可以为1,每个位只能填0/1两种选择,所以根据乘法原理,就有2^x种答案,但是呢,题目中又给了n个可以的,所以要减n。
如何实现:
我相信一般人的思维都是从n个数中,在m个要求中找到不可以为1的,然后在用k减去,就是x。(反正我就是这么干的)
但是大佬们都是这么干的
设p[i]为如果第i位可以为1,那么p[i]为true,反之则反。
这个寻找可以用o(nk)过。
从1~n中每个a[i],判断从0—(k-1)位是否为1,有则为真,无则为反。
这个时候到m个要求了
我们通过题意可以得到,如果他要求中第i为要为1,但是1~n中没有一个数的二进制码第i为1时这个数就不能为1。
所以我们这里运用以一个bz【i】,代表第i位是否可以为1。如果第i个要求 要求g【i】为1,但是p[ g[i] ] 不为true,就是1~n中没有属于他的数,那么bz[ g[i] ] 就为false。
最后统计0到(k-1)中有多少bz为true,这就是x.
注意:
1.判断第i位是否为1,可以用a[i](表示当前要比较的数) & b[i](表示2^i)。(这个可以用,至少到现在我还没错过,但是如果你不行的话,可以用你的智慧的头脑打代码判断)
2. c++不要用 1<<i 表示2^i,反正我错了。
3. 数据大小要用unsigned long long 。否则会炸,但是当x=64时,因为unsigned long long 最大为2^64-1,最小为0,所以你要手动打表(特判),如果此时n不等于0,那么就这么打 printf("%llu\n",18446744073709551616llu-n);
(18446744073709551616为2^64)
大概就是这么个思路吧,如有问题请各位大佬指正。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll unsigned long long
ll ans=0,n,m,c,k,a[1000001],q,pp,b[101];
bool bz[1001],p[1001];
int main()
{
freopen("1.in","r",stdin);
// freopen("zoo.out","w",stdout);
scanf("%lld%lld%lld%lld",&n,&m,&c,&k);
b[0]=1;
for(int i=1;i<=63;i++)
b[i]=b[i-1]*2;
memset(p,false,sizeof(p));
for(int i=1;i<=n;i++)
{
scanf("%llu",&a[i]);
for(int j=0;j<=k-1;j++)
if(a[i]&b[j]) p[j]=true;
}
for(int i=0;i<=k-1;i++)
bz[i]=true;
for(int i=1;i<=m;i++)
{
scanf("%llu%llu",&pp,&q);
bz[pp]=p[pp];
}
ll h=0;
for(int i=0;i<=k-1;i++)
if(bz[i]) h++;
if(h==64 && n==0)
printf("18446744073709551616\n");
else
if(h==64)
printf("%llu\n",18446744073709551615llu-(n-1));
else printf("%llu\n",b[h]-n);
return 0;
fclose(stdin);fclose(stdout);
}