codeforces 1438D,思路非常非常巧妙的構造題

大家好,歡迎來到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題目的最大魅力所在,希望大家都能體會到算法的樂趣。

今天的文章就到這裏,衷心祝願大家每天都有所收穫。如果還喜歡今天的內容的話,請來一個三連支持吧~(點贊、關注、轉發

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章