2018NOIP提高组模拟2(T1,T2)

版权声明:小蒟蒻的博客转载也请注明出处哦 https://blog.csdn.net/qq_42835823/article/details/81906432

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;
}

猜你喜欢

转载自blog.csdn.net/qq_42835823/article/details/81906432