[CF1365G]Secure Password

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
*/

谢谢!!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值