2018NOIP提高组模拟2
———————————————————————————————20180821
1·开锁匠(unlock)
描述
经济危机席卷全球,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
题解
我们可以发现这道题相当于从1到n的开门循环,然后再开余下的门
先处理一下每个i到i+1的错误次数,然后再算出一遍总的错误次数
注意要特殊处理一下开的第一扇门
注意long long
#include<iostream>
#include<cstdio>
using namespace std;
int n,k,key[100010],door[100010],mis[100010],c,d;
long long s[100010];
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d",&key[i]);
door[key[i]]=i;
}
mis[0]=door[1]-1;
if(k==1){
printf("%d",mis[0]);
return 0;
}
k--;
mis[n]=door[1]-door[n];
if(mis[n]<0)mis[n]+=n;
//s[1]=mis[1];
for(int i=1;i<n;i++){
mis[i]=door[i+1]-door[i];
if(mis[i]<0)mis[i]+=n;
s[i]=s[i-1]+mis[i];
}
s[n]=s[n-1]+mis[n];
c=k/n;d=k%n;
printf("%lld",s[n]*c+s[d]+mis[0]);
return 0;
}
2·位运算(xorand)
描述
有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操作。
题解
对于异或:
我们可以构造一棵01串的trie树,每次查询的时候,就尽量找是否有与自己相反的0或1
对于&:
对于每一个新进的数,我们都可以用递归和记忆化标记它的所有子集。
例如13,二进制为1101,则它的子集为0101,1001,1100……(sub[5],sub[11],sub[12]……)并把它们都标记为真。
每一个被标记过的子集,我们不用管他的原来的集合是什么,我们只用知道原来的数肯定存在这么一个子集。
查询的时候,就把这个数二进制从左到右(20–>1)的数,如果是1的话就就查找是否这个子集被标记。
(所以的1都是越居左越好)
对于|:和&差不多
注意trie树的大小。
#include<iostream>
#include<cstdio>
using namespace std;
void read(int &x){
x=0;char c=getchar();
while(c<'0' || c>'9')c=getchar();
while(c>='0' && c<='9'){
x=x*10+c-'0';
c=getchar();
}
}
void write(int x){
if(x==0){putchar(48);return;}
int len=0,dg[20];
while(x>0){dg[++len]=x%10;x/=10;}
for(int i=len;i>=1;--i)putchar(dg[i]+48);
putchar(' ');
}
int n;
int a[11000000][2],t=1,sub[1100000];
void mark(int x){//标记子集
sub[x]=1;
for(int i=20;i>=1;i--)
if((x&(1<<(i-1)))&&!sub[x-(1<<(i-1))])
mark(x-(1<<(i-1)));
//有一且未被标记
}
int askand(int x){
int ans=0;
for(int i=20;i>=1;i--){
if((x&(1<<(i-1)))&&sub[ans+(1<<(i-1))])
ans+=(1<<(i-1));
}
return ans;
}
int askor(int x){
int ans=0,r=0;
for(int i=20;i>=1;i--){
if(x&(1<<(i-1)))ans+=(1<<(i-1));
else{
if(sub[r+(1<<(i-1))]){
r+=(1<<(i-1));
}
}
}
return ans+r;
}
void add(int x){
int r,p=1;
for(int i=20;i>=1;i--){
if(x&(1<<(i-1)))r=1;
else r=0;
if(!a[p][r])a[p][r]=++t;
p=a[p][r];
}
}
int ask(int x){
int r,p=1,ans=0;
for(int i=20;i>=1;i--){
if(x&(1<<(i-1)))r=1;
else r=0;
if(a[p][1-r]){
p=a[p][1-r];
ans+=(1<<(i-1));
}
else p=a[p][r];
}
return ans;
}
int main(){
read(n);
int c,d;
for(int i=1;i<=n;i++){
read(c);read(d);
if(c==1){
mark(d);
add(d);
}
else{
write(ask(d));
write(askand(d));
write(askor(d));
putchar('\n');
}
}
return 0;
}