【思维+位运算】Endfield (hard)(只出现一次的数 扩展为2N+3 常数空间复杂度)

Endfield (hard)

思路源自@温家浩

题目背景

本题与《Endfield (easy)》共享题目背景,简单版本为本题的子问题。若能通过 hard,在做简单修改后,必能通过 easy。

   S u r t r 1 Surtr1 Surtr1 正在 E n d f i e l d Endfield Endfield 里建造流水线,建造期间 S u r t r 1 Surtr1 Surtr1 发现部分传送带摆错方向了,所以他想撤销这部分传送带(想要销毁传送带就必须把传送带上的物件放到仓库和背包内),但是 S u r t r 1 Surtr1 Surtr1的仓库和背包已经满了,他必须销毁部分背包内的物品,且他想销毁那种只存放了一个的物品。
  背包内的物品太多,他很难找到那些单数物品,你能帮他找到这些单数物品吗?

  给定正整数 n n n ,代表非单数物品的个数,和一个长度为 2 n + 3 2n+3 2n+3 的物品编号序列 { a n } \{a_n\} {an}。物品编号序列 { a n } \{a_n\} {an} 满足:每个物品编号均为非负整数,其中有 n n n 个物品编号出现过 2 2 2 次, 3 3 3 个物品编号只出现过 1 1 1 次。满足这 n + 3 n+3 n+3 个物品的编号两两不同。
  求这 3 3 3 个只出现过一次的物品的编号。

说明:
  要求空间复杂度为常数。
  由于牛客的一些限制,空间限制不能开得太小,故而允许使用 O ( N ) O(N) O(N)的空间来储存 { a n } \{a_n\} {an}
  请使用快读来读取数据:

inline long long read()
{
   long long x=0,f=1;
   char ch=getchar();
   while(ch<'0'||ch>'9')
   {
       if(ch=='-')
           f=-1;
       ch=getchar();
   }
   while(ch>='0' && ch<='9')
       x=x*10+ch-'0',ch=getchar();
   return x*f;
}

  快读的使用方法:
  假设输入共一行,两个整数 n n n m m m,中间用空格隔开,那么读取方法为:

int n = read();
int m = read();

输入格式

输入共两行。
第一行,一个正整数 n ( 1 ≤ n ≤ 5 × 1 0 6 ) n(1\le n\le 5\times10^6) n(1n5×106)
第二行,输入一次由 2 n + 3 2n + 3 2n+3 个非负整数组成的序列 { a n } ( 0 ≤ a i ≤ 1 0 18 ) \{a_n\}(0\le a_i\le 10^{18}) {an}(0ai1018)

输出格式

输出共一行,在一行上输出 3 3 3 个非负整数,每个数之间用空格隔开。
每个数表示只出现过一次的非负整数。
按从小到大的顺序输出。

输入输出样例 #1

输入 #1

3
1 1 2 2 3 3 6 4 5

输出 #1

4 5 6

输入输出样例 #2

输入 #2

2
1145141 2333 2333 1000000007 998244353 1145141 1270

输出 #2

1270 998244353 1000000007

题解

2n + 1 的情形

考虑异或运算的两个重要性质:

  1. 恒等律: p ⊕ 0 = p p \oplus 0 = p p0=p
  2. 归零律: p ⊕ p = 0 p \oplus p = 0 pp=0

X = ⨁ i = 1 2 n + 1 a i X = \bigoplus_{i=1}^{2n+1} a_i X=i=12n+1ai,则 X X X 即为答案。

证明: 由归零律,所有出现过两次的数字变成了 0。由于恒等律,那些只出现一次的数字与 0 异或的结果为那个数字。

2n + 2 的情形

我们可以找到一种序列 { a n } \{a_n\} {an} 的划分方式,使得两个只出现一次的数 a x , a y a_x, a_y ax,ay 处于不同的部分。然后按照 2 n + 1 2n + 1 2n+1 的情形,将两部分分别求异或即可。

一种简单且合理的划分方法:首先求出 x = a x ⊕ a y x = a_x \oplus a_y x=axay 的值(等价于求整个序列的异或和),再求出 lowbit ( x ) \text{lowbit}(x) lowbit(x),根据 a i a_i ai 的二进制表示在这一位是否为 1 将序列划分。

时间复杂度 O ( N ) O(N) O(N),空间复杂度 O ( 1 ) O(1) O(1)
这道题没有卡时间复杂度和空间复杂度
时间复杂度 O ( N ) O(N) O(N)做法:位运算、unordered_set(没有卡哈希)
时间复杂度 O ( N ∗ l o g N ) O(N*logN) O(NlogN)的做法也可以接受:离散化、set等都可以解决

#include<bits/stdc++.h>
// #define int long long
#define all(x) (x).begin(), (x).end()


using namespace std;
typedef long long ll;
using i64 =long long;
using i128 =__int128;
template<class T>void debug(const vector<T> & v,int l,int r){for(int i=l;i<=r;i++){cout<<v[i]<<' ';}cout<<'\n';}
template<class T>void debug(const vector<T> & v){for(int i=1;i<v.size();i++){cout<<v[i]<<' ';}cout<<'\n';}
template<class T>void debug(const T & v){cout<<v<<'\n';}
template<class T>bool chmax(T &a,const T& b) {if(a >= b) return false;a = b; return true;}
template<class T>bool chmin(T &a,const T& b) {if(a <= b) return false;a = b; return true;}
mt19937 rng(time(0));
mt19937_64 rng64((unsigned int) chrono::steady_clock::now().time_since_epoch().count());

ll lowbit(ll x){
	return x&(-x);
}

void Main(){
	ll n;
	cin>>n;
	ll x;
	ll ab = 0;
    for(int i=1;i<=2*n+2;i++){
    	cin>>x;
    	ab^=x;
	}
	ll lb = lowbit(ab);
	ll num1=0,num2=0;
    for(int i=1;i<=2*n+2;i++){
    	cin>>x;
    	if(x&lb)num1^=x;
    	else num2^=x;
	}
	if(num1>num2)swap(num1,num2);
	cout<<num1<<' '<<num2;
}



int32_t main(){
    ios::sync_with_stdio(false),cin.tie(nullptr);
    
    int _ = 1;
//     cin>>_;

    while(_--)Main();

    return 0;
}


2n + 3 的情形

记三个只出现一次的数分别为 a x , a y , a z a_x, a_y, a_z ax,ay,az

X = ⨁ i = 1 2 n + 3 a i = a x ⊕ a y ⊕ a z X = \bigoplus_{i=1}^{2n+3} a_i = a_x \oplus a_y \oplus a_z X=i=12n+3ai=axayaz

b i = a i ⊕ X , 1 ≤ i ≤ 2 n + 3 b_i = a_i \oplus X, \quad 1 \leq i \leq 2n + 3 bi=aiX,1i2n+3,则 b x = a y ⊕ a z , b y = a x ⊕ a z , b z = a x ⊕ a y b_x = a_y \oplus a_z, b_y = a_x \oplus a_z, b_z = a_x \oplus a_y bx=ayaz,by=axaz,bz=axay

X n e w = ⨁ i = 1 2 n + 3 b i = b x ⊕ b y ⊕ b z = 0 X_{new} = \bigoplus_{i=1}^{2n+3} b_i = b_x \oplus b_y \oplus b_z = 0 Xnew=i=12n+3bi=bxbybz=0

断言 1: b x , b y , b z b_x, b_y, b_z bx,by,bz 互不相等。
证明 1:

考虑反证法:由 a x , a y , a z a_x, a_y, a_z ax,ay,az 互不相等,且 b x , b y , b z b_x, b_y, b_z bx,by,bz 均不为 0。若 b x = b y b_x = b_y bx=by,则 0 = b x ⊕ b y = ( a y ⊕ a z ) ⊕ ( a x ⊕ a z ) = a x ⊕ a y ≠ 0 0 = b_x \oplus b_y = (a_y \oplus a_z) \oplus (a_x \oplus a_z) = a_x \oplus a_y \neq 0 0=bxby=(ayaz)(axaz)=axay=0,矛盾。

接下来令 f ( x ) = lowbit ( x ) f(x) = \text{lowbit}(x) f(x)=lowbit(x)

断言 2: f ( b x ) , f ( b y ) , f ( b z ) f(b_x), f(b_y), f(b_z) f(bx),f(by),f(bz) 中有且仅有两个数相等。
证明 2:

不妨设这三个数中最小的数是 f ( b x ) f(b_x) f(bx),令 n = f ( b x ) n=f(b_x) n=f(bx),则由 X n e w = 0 X_{new} = 0 Xnew=0 知, b y , b z b_y, b_z by,bz 中定然有一个数在第 n n n 位为 1,另一个数在该位上为 0。不妨设 b y b_y by 在该位上为 , f ( b x ) = f ( b y ) , f ( b z ) > f ( b y ) = f ( b x ) f(b_x)=f(b_y),f(b_z)>f(b_y)=f(b_x) f(bx)=f(by),f(bz)>f(by)=f(bx)

f ( b x ) = f ( b y ) f(b_x) = f(b_y) f(bx)=f(by), m m m b z b_z bz 最低位所在位置。
断言 2 { b n } \{ b_n \} {bn} 中所有数对应的 f ( b i ) f(b_i) f(bi) 的异或和为 f ( b z ) f(b_z) f(bz)。由 b z b_z bz 在第 m m m 位上为 1 1 1,且 b x ⊕ b y ⊕ b z = 0 , b_x \oplus b_y \oplus b_z = 0, bxbybz=0不妨设 b y b_y by 在该位为 1 1 1 b x b_x bx 在该位为 0 0 0。故可按照 b i b_i bi 在第 m m m 位上是否为 1 1 1,将 { b n } \{ b_n \} {bn} 分成两个部分。每部分的异或值恰好为 b x b_x bx,那么 a x = b x ⊕ X a_x=b_x\oplus X ax=bxX

然后,问题就转化为 2 n + 2 2n + 2 2n+2 的情形,共需读入序列 { a n } \{ a_n \} {an} 四次(由于牛客所开空间的限制,这里只读一次,并用数组存储)。

时间复杂度 O ( N ) O(N) O(N),空间复杂度 O ( 1 ) O(1) O(1)

#include<bits/stdc++.h>
// #define int long long
#define all(x) (x).begin(), (x).end()

using namespace std;
typedef long long ll;
using i64 =long long;
using i128 =__int128;
template<class T>void debug(const vector<T> & v,int l,int r){for(int i=l;i<=r;i++){cout<<v[i]<<' ';}cout<<'\n';}
template<class T>void debug(const vector<T> & v){for(int i=1;i<v.size();i++){cout<<v[i]<<' ';}cout<<'\n';}
template<class T>void debug(const T & v){cout<<v<<'\n';}
template<class T>bool chmax(T &a,const T& b) {if(a >= b) return false;a = b; return true;}
template<class T>bool chmin(T &a,const T& b) {if(a <= b) return false;a = b; return true;}
mt19937 rng(time(0));
mt19937_64 rng64((unsigned int) chrono::steady_clock::now().time_since_epoch().count());

ll lowbit(ll x){
	return x&(-x);
}

void Main(){
	ll n,x;
	cin>>n;
	vector<ll> ans(4);
	ll xor1 = 0;
	//求a[i]异或和 
    for(int i=1;i<=2*n+3;i++){
    	cin>>x;
    	xor1^=x;
	}
	
	ll lb1 =0;
	//求f[b[i]] 异或和 
	for(int i=1;i<=2*n+3;i++){
		cin>>x;
		lb1^=lowbit(x^xor1);
	}
	//划分为两部分 
	ll xor2 =0;
	for(int i=1;i<=2*n+3;i++){
		cin>>x;
		if((x^xor1)&lb1)xor2^=(x^xor1);
	}
	//第一个数 
	ans[1]=xor1^xor2;
	
	//求第二个数
	ll lb2 = lowbit(ans[1]^xor1);
	if(ans[1]&lb2)ans[2]=ans[1];
	else ans[2]=0;
    for(int i=1;i<=2*n+3;i++){
    	cin>>x;
    	if(x&lb2)ans[2]^=x;
	}
	ans[3]=xor1^ans[1]^ans[2];
	sort(ans.begin()+1,ans.end());
	cout<<ans[1]<<' '<<ans[2]<<' '<<ans[3]<<'\n';
}


int32_t main(){
    ios::sync_with_stdio(false),cin.tie(nullptr);
    
    int _ = 1;
//     cin>>_;

    while(_--)Main();

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值