开锁匠 unlock.cpp
【描述】 经济危机席卷全球,L国也收到冲击,大量人员失业。 然而,作为 L 国的风云人物,X 找到了自己的新工作。从下周开始,X 将成为一个 酒店的助理锁匠,当然,他得先向部门领导展示他的开锁能力。 领导给了 X 一串钥匙,这串钥匙串在一个大圆环上,每把钥匙有一个编号 (1..N)。然后蒙上 X 的眼睛并把他带到一个圆形的大房间中。在这个房间中有 N 个上锁 的门,用 1..N 表示,这串 N 把钥匙每一把正好打开一扇门(钥匙编号和门编号一致就可以 打开)。 X 的工作就是打开每扇门。他因为蒙着眼睛,不过可以沿着房间的墙壁移动,不能 改变方向,直到他摸着一扇门,然后他会尝试用第一把钥匙(最左边)来打开门,如果钥 匙不能打开门,他会将钥匙移到另外一侧(最右边),重复这样直到找到正确的钥匙,当 他把所有门打开就结束任务。不过 X不知道的是,领导并不是测试 他开锁能力,而是测试 他的耐心,所以领导故意把 X 带到圆形房间,这样 X 每开一扇门后,领导就会在后面悄悄 把门再次锁上,这样以来,X 打开最后一扇门后又回到第一扇门然后一直重复下去。不过 X 是一个勤奋和耐心的人,他一直毫无怨言的做着这件事,不说任何抱怨的话,只是在每 开一扇门他会默默的统计自己已经错误了多少次,不过慢慢时间太久他的计算能力不足, 需要你来帮助他计算错误的次数。
任务:给定数字 k,回答当 X打开第 k扇门时,一共错误了多少次?。 【输入】 第一行是 2个整数 N,K 接下来 N 行,每行包含一个整数 Vi,表示钥匙串从第一把(左侧)到最后一把,第 i 把钥 匙的编号
【输出】 一个整数,回答第 k次打开一扇门,已经错误的次数
【输入样例】
4 6
4
2
1
3
【输出样例】 13
【样例解释】
打开第 1扇门的尝试(1号门):4 2 1 3,错误 2次,打开后钥匙排列:1 3 4 2
打开第 2扇门的尝试(2号门):1 3 4 2,错误 3次,打开后钥匙排列:2 1 3 4
打开第 3扇门的尝试(3号门):2 1 3 4,错误 2次,打开后钥匙排列:3 4 2 1
打开第 4扇门的尝试(4号门):3 4 2 1,错误 1次,打开后钥匙排列:4 2 1 3
打开第 5扇门的尝试(1号门):4 2 1 3,错误 2次,打开后钥匙排列:1 3 4 2
打开第 6扇门的尝试(2号门):1 3 4 2,错误 3次,打开后钥匙排列:2 1 3 4
总错误 13 次
【子任务】 40%数据:1<=N,K<=1000 60%数据:1<=K<=50000
100%数据:1<=N<=100000,1<=Vi<=N,1<=K<=10^9
每n个门循环一次,但是注意第一号门不能计入循环中。例如:3 4 1 2 -> 1 2 3 4-> 2 3 4 1 -> 3 4 1 2-> 4 1 2 3 ->1 2 3 4......
记录一下第i号钥匙到第i+1号钥匙的次数即可。
#include<bits/stdc++.h>
#define ll long long
#define MAXN 100002
using namespace std;
int N, K, first_key;
int key[MAXN];
ll miss[MAXN];
int main() {
scanf("%d%d",&N,&K);
for (int i=0;i<N;++i) {
int x; scanf("%d",&x);
key[--x]=i;
if(i==0) first_key=x;
}
ll sol=(ll)abs(key[0]-key[first_key]);
if (key[first_key]>key[0]) sol = N-sol;
for (int i=1;i<=N;++i) {
int curr=key[i-1],prev=key[(i-2+N)%N];
int wrong=abs(curr-prev);
if (prev>curr) wrong=N-wrong;
miss[i]=miss[i-1]+(ll)wrong;
}
sol+=miss[N]*(K/N);
sol+=miss[K%N]-miss[1];
printf("%lld\n",sol);
return 0;
}
位运算 xorand.cpp
【描述】 有 q次操作,每次操作是以下两种:
1、 加入一个数到集合中
2、 查询,查询当前数字与集合中的数字的最大异或值,最大 and值,最大 or值
【输入】 第一行 1个正整数 Q表示操作次数 接下来 Q 行,每行 2 个数字,第一个数字是操作序号 OP(1,2),第二个数字是 X 表示操 作的数字
【输出】 输出查询次数行,每行 3 个整数,空格隔开,分别表示最大异或值,最大 and 值,最大 or 值
【输入样例 1】
5
1 2
1 3
2 4
1 5
2 7
【输出样例 1】
7 0 7
5 5 7
【样例解释 1】
询问 4时,已插入 2、3,最大异或值为 4^3=7,最大 and值为 4&3或 4&2=0,最大 or值为 4|3=7
询问 7 时,已插入 2、3、5,最大异或值为 7^2=5,最大 and 值为 7&5=5,最大 or 值为 7|2=7|3=7|5=7
【输入样例 2】 10
1 194570
1 202332
1 802413
2 234800
1 1011194
2 1021030
2 715144
2 720841
1 7684
2 85165
【输出样例 2】
1026909 201744 1032061
879724 984162 1048062
655316 682376 1043962
649621 683464 1048571
926039 85160 1011199
【子任务】
对于%10的数据 1<=Q<=5000
对于另%10的数据保证 X<1024
对于另%40的数据保证 1<=Q<=100000
对于所有数据保证 1<=Q<=1000000,1<=X<=2^20 保证第一个操作为 1操作。
对于异或运算,用trie树。树上存储集合中所有数的二进制,用01串表示。当查询x的异或最大值时,从最高位开始查,若x的该位为1,则在对应位置上找0,反之找1.贪心完后得到一个数,该数一定在集合中,且与x的异或值最大。
对于and,只需要考虑x的二进制中为1的位置。建立一个数组le,记录每个数的子集。
例如:11对应1011,那么它可以提供的子集有1011,1010,1001,0011,1000,0010,0001,0000;
求and时,尽可能在x的1的位置上填1,这样能使and值最大。求法如下:记录一个数now表示找到的最大值,从高位向下枚举,1的位置上如果能找到1,那么看now补上这个1后是否在le中,在的话就补上,不在就继续向下枚举。最后now&x得到答案。
对于or,和and类似,只需要考虑x的二进制中为0的位置。代码见下:
#include<bits/stdc++.h>
using namespace std;
int root=1,tot=1,trans[(1<<21)][2],xx,op,le[1<<21];
void Markless(int x){
le[x]=1;
for(int j=0;j<20;++j)
if((x>>j&1)&&(!le[x^(1<<j)]))
Markless(x^(1<<j));
}
void insert(int x){
int p=root;
for(int j=19;~j;--j){
int c=(x>>j&1? 1:0);
if(!trans[p][c]) trans[p][c]=++tot;
p=trans[p][c];
}
}
int ask(int x){
int p=root,now=0;
for(int j=19; ~j; --j){
int c=(x>>j&1? 0:1);
if (trans[p][c]){
p=trans[p][c];
if(c) now|=1<<j;
}
else{
p=trans[p][c^1];
if(c^1) now|=1<<j;
}
}
return now;
}
int main(){
int Q;scanf("%d",&Q);
for(int i=1;i<=Q;++i){
scanf("%d%d",&op,&xx);
if(op==1){
Markless(xx);
insert(xx);
}
if(op==2){
printf("%d ",ask(xx)^xx);
int now=0;
for(int j=19;~j;--j){
if(xx>>j&1&&le[now|1<<j]) now|=1<<j;
}
printf("%d ",now&xx);
now=0;
for(int j=19;~j;--j){
if(!(xx>>j&1)&&le[now|1<<j]) now|=1<<j;
}
printf("%d\n",now|xx);
}
}
}