Secure Password
题解
挺水的一道构造题
由于只能询问13次,所以我们的询问次数只能比
l
o
g
n
log\, n
logn多一点点,常数甚至不能超过1.5。那个黑心出题人会这样卡
而
l
o
g
n
log\, n
logn的询问次数有令我们想到了二进制拆分。
但是,由于
[
1
,
1000
]
[1,1000]
[1,1000]的下标的二进制位有包含的关系,我们不得不把每位为1与每位为0的都跑一遍,询问次数达到了
2
l
o
g
n
2log_{n}
2logn,肯定过不了。
但考虑一下,我们之所以需要正反都跑一遍是因为存在包含的关系,如何使得下标之间不存在包含关系呢?
离散化可以解决这个问题。当然,这里是让值变得更散。我们发现,由于询问最高可达13次,我们可以把下标离散成小于
2
13
2^{13}
213的数。
又要让离散后的数没有包含的关系,所有离散的数的为1的二进制位必须相等。当然,不相等也可以做,但太麻烦了。
我们发现
C
13
5
=
1287
>
1000
C_{13}^{5}=1287>1000
C135=1287>1000,所以我们离散的数为1的位数为5就已经可以过了,但为了让其价值最大化,还是枚举6位。
我们将每个下标离散成有6个位为1的数后,对于每一个二进制位,求出当前位上为1的下标的或和值即可。容易证明,对于一个数,其取反的位数的为1位的或和值刚好包含除它以外的所有数。
源码
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;
#define MAXN 1005
typedef long long LL;
const int INF=0x7f7f7f7f;
typedef unsigned long long uLL;
typedef pair<int,int> pii;
template<typename _T>
void read(_T &x){
_T f=1;x=0;char s=getchar();
while('0'>s||'9'<s){if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
x*=f;
}
int n,id[MAXN];LL w[20];
vector<int> vec[20];
signed main(){
read(n);int lim=0,num=(1<<6)-2;
while(lim<n){
num++;
if(__builtin_popcount(num)!=6)continue;lim++;
//printf("%d %d\n",lim,num);
for(int i=0;i<13;i++)
if(!((num>>i)&1))vec[i].push_back(lim);
id[lim]=num;
}
for(int i=0;i<13;i++){
if(vec[i].empty())continue;
int siz=vec[i].size();printf("? %d",siz);
for(int j=0;j<siz;j++)
printf(" %d",vec[i][j]);
puts("");fflush(stdout);read(w[i]);
}
printf("!");
for(int i=1;i<=n;i++){
LL ans=0;
for(int j=0;j<13;j++)
if((id[i]>>j)&1)ans|=w[j];
printf(" %lld",ans);
}
puts("");
return 0;
}
/*
1111110000000
0000001111110
*/