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