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(1≤n≤5×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}(0≤ai≤1018)。
输出格式
输出共一行,在一行上输出
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 的情形
考虑异或运算的两个重要性质:
- 恒等律: p ⊕ 0 = p p \oplus 0 = p p⊕0=p
- 归零律: p ⊕ p = 0 p \oplus p = 0 p⊕p=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=ax⊕ay 的值(等价于求整个序列的异或和),再求出 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(N∗logN)的做法也可以接受:离散化、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=ax⊕ay⊕az。
则 b i = a i ⊕ X , 1 ≤ i ≤ 2 n + 3 b_i = a_i \oplus X, \quad 1 \leq i \leq 2n + 3 bi=ai⊕X,1≤i≤2n+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=ay⊕az,by=ax⊕az,bz=ax⊕ay。
令 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=bx⊕by⊕bz=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=bx⊕by=(ay⊕az)⊕(ax⊕az)=ax⊕ay=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,
bx⊕by⊕bz=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=bx⊕X。
然后,问题就转化为 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;
}