大家好,歡迎來到codeforces專題。
今天選擇的問題是contest1438的D題,全場通過人數為1325人。一般在codeforces當中千人通過的題難度都不算太高,但是這題有點例外,雖然沒有涉及一點高深的演算法,但是想要自己做出來還是有點難度的。
題目連結:https://codeforces.com/contest/1438/problem/D
廢話不多說了,我們直接來看題。
題意
給定一個擁有n()個正整數的陣列,然後我們隊陣列當中的元素進行一種神奇的異或操作。
操作如下,我們選擇三個不同的下標i、j、k。對a[i]
,a[j]
,a[k]
這三個元素執行異或操作,即。之後這三個數分別賦值成這個異或之後的結果。
現在我們想要在n步這樣的神奇異或操作之內讓陣列當中的所有元素全部相等,請問這一點是否可能呢?首先輸出YES或NO,表示是否有解。如果有解輸出需要操作的步數,以及對應選擇的元素下標。
樣例
在第一個樣例當中,4、1、7的異或結果為2,所以通過這樣一步操作之後,即可以滿足所有元素全部均等。
題解
我一開始的時候慣性思維,既然是異或運算,那麼肯定要從二進位制下手。一個陣列當中的所有元素均等,其實就等價於它們在每一個二進位制位上也等相等,同為0或者是同為1。於是我就一直在思考怎麼來針對每一個二進位制位來進行判斷和選擇,不知不覺就走進了死衚衕,因為這些二進位制位之間彼此影響, 我們很難一位一位地梳理清楚。
我之所以走進死衚衕是因為被題目當中的一個條件給欺騙了,這個條件就是最多n個操作步驟的限制。我們直觀上都會覺得這是一個非常嚴苛的要求,所以會期望想到一個完美的解法,可以用最少的步驟解開這個問題。
但實際上這個n足夠大,足夠一些看起來非常笨的方法也能AC。不得不說這也是很多題目當中慣用的思維陷阱,考驗的就是選手的膽量和經驗。
異或的性質
首先我們來分析一下異或運算,這題當中並沒有對異或做什麼特殊的處理。唯一不同的地方就是,我們是對三個數進行異或。我們從最基礎的01二進位制位來分析,3個數做異或只有四種情況。000、010、011和111,我們發現其中000和011的結果都是0,010和111的結果是1。因為異或相同為0,不同為1的計算特性,會導致相同的數被消除。
比如我們計算的三個數是[a,b,b]
那麼最後的結果是a,我們可以利用這一點來做文章。想起來或許有些複雜,但是說穿了真的一文不值。
我們假設n=7,這7個數分別是[a,b,c,d,e,f,g]
。首先我們對前三個數進行異或操作,這樣我們會得到:[h,h,h,d,e,f,g]
,接著我們選擇第3、4、5位的元素操作,得到:[h,h,j,j,j,f,g]
。我們繼續選擇第5、6、7位的元素進行操作,得到[h,h,j,j,k,k,k]
。
到這裡其實已經有點眉目了,因為[a,b,b]
的操作結果是a,我們剩下要做的就是繼續選擇,把除了k之外其他的元素全部消除。
我們繼續選擇第3、4、5位的元素操作,得到[h,h,k,k,k,k,k]
,同理我們最後選擇第1、2、3位的元素操作,這樣整個陣列當中的元素都變成了k。到這裡,我們一共進行了5次,也就是n-2次操作,完全沒有超過題目的限制。
但是這裡有一個小問題,這個方案之所以可行是有前提的。它最大的前提就是n是奇數。如果n是偶數,就會最後剩下一個元素,這個應該怎麼解決呢?
偶數的情況
偶數的情況我們光想是很難想出辦法來的,因為我們解決不了最後多餘一個元素的問題。
這裡需要用到一個關鍵性的推論,這個推論非常隱蔽,真的不容易想到。我們假設,當n為偶數時,那麼無論我們對這n個元素如何操作,這個異或得到的k保持不變。
這個結論是從哪裡來的?其實也是從異或的性質當中來的。我們對三個數做異或,從具體某一個二進位制位來分析。我們會發現我們的操作不會改變整體這n個bit的奇偶性。對於異或操作而言,兩兩相消,最後的結果只和奇偶性相關。最終的結果只和這個奇偶性相關。
從這一點出發,我們進一步可以得到如果,那麼一定無解。
這個結論其實也很簡單,因為我們已經知道了,無論我們如何操作也不會改變這個k值。由於n是偶數,所以如果n個數完全相等的話,那麼它們的異或值一定等於0,所以k不等於0的時候,一定無解。
當k等於0的時候怎麼辦呢?其實非常簡單,我們只需要拋棄掉最後一個元素,把之前的n-1個元素按照上面n為奇數時的操作全部操作相等即可。這樣一番操作之後,陣列會變成這樣[a,a,a,a...a,b]
。前面n-1個a的異或值為a,而整體n個數的異或值為0,所以可以得到a=b。那麼我們就完成了整個操作。
整個思路有了之後,程式碼實現就太簡單了。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <cmath>
#include <cstdlib>
#include <string>
#include <map>
#include <set>
#include <algorithm>
#include <functional>
#define rep(i,a,b) for (int i=a;i<b;i++)
#define Rep(i,a,b) for (int i=a;i>=b;i--)
#define foreach(e,x) for (__typeof(x.begin()) e=x.begin();e!=x.end();e++)
#define mid ((l+r)>>1)
#define lson (k<<1)
#define rson (k<<1|1)
#define MEM(a,x) memset(a,x,sizeof a)
#define L ch[r][0]
#define R ch[r][1]
const int N=100050;
const long long Mod=1000000007;
using namespace std;
int a[N];
int main() {
int n, x;
scanf("%d", &n);
rep(i, 0, n) {
scanf("%d", a + i);
}
// 如果n為奇數,一定有解
if (n % 2) {
puts("YES");
printf("%d\n", n-2);
for (int i = 0; i + 2 < n; i+=2) {
printf("%d %d %d\n", i+1, i+2, i+3);
}
for (int i = n-3; i - 2 >= 0; i-=2) {
printf("%d %d %d\n", i+1, i, i-1);
}
}else {
// 如果n為偶數,判斷整個陣列的異或值是否為0
x = 0;
rep(i, 0, n) x ^= a[i];
if (x > 0) {
puts("NO");
}else {
n --;
puts("YES");
printf("%d\n", n-2);
for (int i = 0; i + 2 < n; i+=2) {
printf("%d %d %d\n", i+1, i+2, i+3);
}
for (int i = n-3; i - 2 >= 0; i-=2) {
printf("%d %d %d\n", i+1, i, i-1);
}
}
}
return 0;
}
到這裡,今天這道題就做完了,怎麼樣,今天的題目還挺有意思吧。講道理把演算法講出來之後非常簡單,幾乎沒有難度,但是如果讓我們自己思考,會變得非常難,我們很難從當中整理出思緒來。思路巧妙有趣這也是codeforces題目的最大魅力所在,希望大家都能體會到演算法的樂趣。
今天的文章就到這裡,衷心祝願大家每天都有所收穫。如果還喜歡今天的內容的話,請來一個三連支援吧~(點贊、關注、轉發)