題目請參考https://zerojudge.tw/ShowProblem?problemid=i629.
這題要做得快, 要仔細分析題目的四個操作, 能解決操作最慢的那個, 才會AC. 哪個操作最花時間呢?操作1到3都要排序, 排序很快的方法有很多, 不會隨著資料的增加而大幅增加排序時間,所以問題不大.
操作4看起來最花時間,如果所有數字都去做XOR,測資又一直在做操作4就GG了.而且做了XOR的數字順序一定大亂,每做一次操作4,就全部要重新排序,實在太花時間,所以要解決的重點就是操作4.
我們先來研究一下XOR到底在做什麼. 題目說k≤109,所以k是非負整數(unsigned int),沒有處理正負號的問題, XOR就是對每個位元做位元運算. 而且我們知道不管k是多少,任何數字XOR k兩次,就跟沒有發生什麼事情一樣,所以先別急著重新排序,只要把k記錄起來就好. XOR還有一個特性, XOR k1再XOR k2, 會等於XOR (k1 XOR k2 ),所以不管XOR幾個數字k, 都先把那些數字k XOR起來就可以了,這樣就省了很多重新排序時間了.
再仔細看看XOR後的數字,真的需要重新排序嗎? 用一個位元(bit)來看,如果XOR 1, 那這bit會反相, 0變成1,1變成0. 如果是XOR 0,那這bit不變. 所以XOR k時,就可以把k拆成每個bit來看,看看哪些是要反相就可以知道他真正代表0或是1了. 然後在做操作1到3時, 就根據紀錄的k值來得到每個bit真正的值來加入,刪除或排序.
由於對每個k要一個bit一個bit來看,所以對每個數字k都拆成bit來排序,我用二元樹來儲存每個bit,這樣刪除和插入都快. 從main來看就像下面這樣.
TREE tree;
int main() {
int q;
scanf("%d",&q);
while (q--) {
int op, k;
scanf("%d %d",&op,&k);
int ret;
switch(op){
case 1://插入一個數字 k 到序列中
tree.Add(k);
break;
case 2://刪除序列中的一個數字 k
ret=tree.Delete(k);
if(ret==-1){
printf("HEHE\n");
}
break;
case 3://輸出第 k 小數字
ret=tree.Find(k);
if(ret==-1){
printf("QQ\n");
}else{
printf("%d\n",ret);
}
break;
case 4://把序列中所有數字 xor k
tree.XOR(k);
break;
}
}
}
C++組成樹的節點node, 用來儲存每個bit, 因為bit有1和0兩種值,如果這個bit有1就在left加入一個node,有0就在right加入一個node. node看起來像下面這樣. 順便一提那個SUM是為了加快操作3速度,SUM代表這個節點有紀錄幾個數字. 所以看ROOT.SUM就可以知道要不要印出QQ.
class NODE {
public:
NODE *Left;
NODE *Right;
int SUM;//紀錄幾個數字
NODE(){
Left=Right=NULL;
SUM=0;
}
void Add(unsigned int k, unsigned int bit, unsigned int xor_v);
int Delete(unsigned int k, unsigned int bit, unsigned int xor_v);
int Find(unsigned int k, unsigned int bit, unsigned int xor_v,
unsigned int v);
};
C++樹就用node來做操作1到3. 操作4的XOR值則在tree裡記錄在XOR_V,要操作時再傳給node就好.只有一行搞定,變得非常簡單. ROOT是根結點, 代表k最左邊的那個bit, 所以從0x80000000開始,下一層的參數只要右移一個bit.
class TREE {
public:
int XOR_V;
NODE ROOT;//根節點
TREE(){
XOR_V=0;
}
void Add(unsigned int k){
ROOT.Add(k,0x80000000,XOR_V);
}
int Delete(unsigned int k){
return ROOT.Delete(k,0x80000000,XOR_V);
}
int Find(unsigned int k){
if(ROOT.SUM<k){
return -1;
}
return ROOT.Find(k,0x80000000,XOR_V,0);
}
void XOR(unsigned int k){ //操作4
XOR_V = XOR_V ^ k;
}
};
C++先從node的Add講起好了, 判斷k & bit來知道要處理的這個bit是1還是0, 判斷xor_v & bit才知道要把這個bit正常對待還是反相處理. 正常處理就是1要加在左邊,0就加在右邊的結點. 反向處理就顛倒.所以寫好正常處理的部分再改反相處理部分就好.
void NODE::Add(unsigned int k, unsigned int bit, unsigned int xor_v){
SUM++;
if(!bit){
return;
}
if(k & bit){
if(xor_v & bit){//反相處理
if(!Right){
Right=new NODE();
}
Right->Add(k, bit>>1, xor_v);
}else{//正常處理
if(!Left){
Left=new NODE();
}
Left->Add(k, bit>>1, xor_v);
}
}else{
if(xor_v & bit){//反相處理
if(!Left){
Left=new NODE();
}
Left->Add(k, bit>>1, xor_v);
}else{//正常處理
if(!Right){
Right=new NODE();
}
Right->Add(k, bit>>1, xor_v);
}
}
}
C++刪除操作和Add差不多.但是有可能要刪除的數字不存在,只要任一個bit沒找到結點,就回傳-1代表這數字不存在. 如果每個bit都有找到結點就回傳0, 順便把SUM減一,代表刪了一個數字. 當SUM歸零時,把指標清成NULL,下次刪除才不會誤判有這個數字.
int NODE::Delete(unsigned int k, unsigned int bit, unsigned int xor_v){
if(!bit)
return 0;
if(k & bit){
if(xor_v & bit){//反相處理
if(Right){
int ret=Right->Delete(k, bit>>1, xor_v);
if(ret==0){ //found
SUM--;
if(SUM==0){
Right=NULL;
}
}
return ret;
}else{
return -1; //unfound
}
}else{//正常處理
if(Left){
int ret=Left->Delete(k, bit>>1, xor_v);
if(ret==0){ //found
SUM--;
if(SUM==0){
Left=NULL;
}
}
return ret;
}else{
return -1; //unfound
}
}
}else{
if(xor_v & bit){//反相處理
if(Left){
int ret=Left->Delete(k, bit>>1, xor_v);
if(ret==0){ //found
SUM--;
if(SUM==0){
Left=NULL;
}
}
return ret;
}else{
return -1; //unfound
}
}else{//正常處理
if(Right){
int ret=Right->Delete(k, bit>>1, xor_v);
if(ret==0){ //found
SUM--;
if(SUM==0){
Right=NULL;
}
}
return ret;
}else{
return -1; //unfound
}
}
}
return 0;
}
C++操作3尋找第k個數字是用SUM來計算. 因為數字排序是由小到大,所以正常處理時先檢查是不是在右邊結點.如果右邊的SUM不夠多, 那k就把右邊的SUM減掉, 去左邊找一定會找到. v是紀錄找到的每個bit,32個bit都湊齊後,就是要找的數字.
int NODE::Find(unsigned int k, unsigned int bit, unsigned int xor_v,
unsigned int v)
{
if(bit==0)
return v;
if(xor_v & bit){//反相處理
if(Left){
if(Left->SUM>=k){
return Left->Find(k,bit>>1,xor_v, v);
}else{
k-=Left->SUM;
}
}
if(Right){
return Right->Find(k,bit>>1,xor_v, v|bit);
}
return v;//兩邊都已找完,回傳v
}else{ //正常處理
if(Right){
if(Right->SUM>=k){
return Right->Find(k,bit>>1,xor_v, v);
}else{
k-=Right->SUM;
}
}
if(Left){
return Left->Find(k,bit>>1,xor_v, v|bit);
}
return v;//兩邊都已找完,回傳v
}
return -1;
}
C++全部AC約花0.3秒. 下面附上完整程式.
#include <stdio.h>
#include <iostream>
using namespace std;
class NODE {
public:
NODE *Left;
NODE *Right;
int SUM;//紀錄幾個數字
NODE(){
Left=Right=NULL;
SUM=0;
}
void Add(unsigned int k, unsigned int bit, unsigned int xor_v);
int Delete(unsigned int k, unsigned int bit, unsigned int xor_v);
int Find(unsigned int k, unsigned int bit, unsigned int xor_v,
unsigned int v);
};
void NODE::Add(unsigned int k, unsigned int bit, unsigned int xor_v){
SUM++;
if(!bit){
return;
}
if(k & bit){
if(xor_v & bit){//反相處理
if(!Right){
Right=new NODE();
}
Right->Add(k, bit>>1, xor_v);
}else{//正常處理
if(!Left){
Left=new NODE();
}
Left->Add(k, bit>>1, xor_v);
}
}else{
if(xor_v & bit){//反相處理
if(!Left){
Left=new NODE();
}
Left->Add(k, bit>>1, xor_v);
}else{//正常處理
if(!Right){
Right=new NODE();
}
Right->Add(k, bit>>1, xor_v);
}
}
}
int NODE::Delete(unsigned int k, unsigned int bit, unsigned int xor_v){
if(!bit)
return 0;
if(k & bit){
if(xor_v & bit){//反相處理
if(Right){
int ret=Right->Delete(k, bit>>1, xor_v);
if(ret==0){ //found
SUM--;
if(SUM==0){
Right=NULL;
}
}
return ret;
}else{
return -1; //unfound
}
}else{//正常處理
if(Left){
int ret=Left->Delete(k, bit>>1, xor_v);
if(ret==0){ //found
SUM--;
if(SUM==0){
Left=NULL;
}
}
return ret;
}else{
return -1; //unfound
}
}
}else{
if(xor_v & bit){//反相處理
if(Left){
int ret=Left->Delete(k, bit>>1, xor_v);
if(ret==0){ //found
SUM--;
if(SUM==0){
Left=NULL;
}
}
return ret;
}else{
return -1; //unfound
}
}else{//正常處理
if(Right){
int ret=Right->Delete(k, bit>>1, xor_v);
if(ret==0){ //found
SUM--;
if(SUM==0){
Right=NULL;
}
}
return ret;
}else{
return -1; //unfound
}
}
}
return 0;
}
int NODE::Find(unsigned int k, unsigned int bit, unsigned int xor_v,
unsigned int v)
{
if(bit==0)
return v;
if(xor_v & bit){//反相處理
if(Left){
if(Left->SUM>=k){
return Left->Find(k,bit>>1,xor_v, v);
}else{
k-=Left->SUM;
}
}
if(Right){
return Right->Find(k,bit>>1,xor_v, v|bit);
}
return v;//兩邊都已找完,回傳v
}else{ //正常處理
if(Right){
if(Right->SUM>=k){
return Right->Find(k,bit>>1,xor_v, v);
}else{
k-=Right->SUM;
}
}
if(Left){
return Left->Find(k,bit>>1,xor_v, v|bit);
}
return v;//兩邊都已找完,回傳v
}
return -1;
}
class TREE {
public:
int XOR_V;
NODE ROOT;//根節點
TREE(){
XOR_V=0;
}
void Add(unsigned int k){
ROOT.Add(k,0x80000000,XOR_V);
}
int Delete(unsigned int k){
return ROOT.Delete(k,0x80000000,XOR_V);
}
int Find(unsigned int k){
if(ROOT.SUM<k){
return -1;
}
return ROOT.Find(k,0x80000000,XOR_V,0);
}
void XOR(unsigned int k){ //操作4
XOR_V = XOR_V ^ k;
}
};
TREE tree;
int main() {
int q;
scanf("%d",&q);
while (q--) {
int op, k;
scanf("%d %d",&op,&k);
int ret;
switch(op){
case 1://插入一個數字 k 到序列中
tree.Add(k);
break;
case 2://刪除序列中的一個數字 k
ret=tree.Delete(k);
if(ret==-1){
printf("HEHE\n");
}
break;
case 3://輸出第 k 小數字
ret=tree.Find(k);
if(ret==-1){
printf("QQ\n");
}else{
printf("%d\n",ret);
}
break;
case 4://把序列中所有數字 xor k
tree.XOR(k);
break;
}
}
}
C++