1. 程式人生 > >2018NOIP提高組模擬2(T1,T2)

2018NOIP提高組模擬2(T1,T2)

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