Codeforces 665E. Beautiful Subarrays (字典樹)

題目鏈接:http://codeforces.com/problemset/problem/665/E

                     (http://120.78.128.11/Problem.jsp?pid=2255)

題意:找出有多少個連續的區間[l,r](1  ≤  l  ≤  r  ≤  n),該區間中所有的數的異或值大於等於k;

思路:首先,如果是單看題目的話,會發現暴力的話複雜度是O(n^3),但是我們先預處理異或前綴和,然後你會發現[l,r]區間的異或和等於s[l-1]^s[r],這樣就可以O(n^2)的求得答案了,但是因爲n是1e6,也就是說暴力絕對超時,因爲時間纔開了3s(雖然說cf裏一秒時限,1e10複雜度也可以莽一莽,但是這個是1e12啊!!)。然後,我們會想,因爲是異或,而且是要比較,於是乎我們就想到了字典樹(不要問爲什麼,我就是這樣想到的)。然後我們可以開始一波分析:

{

         假設:全是5位二進制(默認補全到5位)

         前提:S[i]是前i個數的異或和,  K爲題目裏的K,  A是S[i]^A=K 即S[i]^K=A

         S[i] = 5

         K  = 24

         從高位到低位存入字典樹時(默認已經存入,現在在find操作):

         {

                   S[i]:00101

                   K : 11000

                  第五位時:k=1,則說明這一位必須往S[i]第五位的異或值方向走,因爲我們現在是在查A在字典樹上的路徑,至於爲什麼只跑這個數就可以求答案,我們慢慢來。

     第四位同理;

       第三位時:(#敲黑板劃重點)這個時候K的第三位爲0,按照我說的來看,我們A在樹上的路徑,那麼此時我們要跑的下一步是S[i]的第三位和K第三位的異或值也就是A的第三位,那麼如果我下一步不往這個方向走呢(此時A的第三位等於S[i]的第三位)?我往S[i]的第三位異或1的方向跑(反向),那麼那個方向延伸的樹枝上的所有數字就都是大於K的(這裏不做解釋),也就是我們雖然不需要跑那裏,但是我們要加上經過那個延伸出去的樹枝包含的數字個數;

     第二位、第一位同第三位的代碼;

 

              在跑完所有的情況的時候,我們就求出了所有的A,A滿足S[i]^A>k,沒錯,還少,還少一個等於的情況,此時,我們只需要判斷我們是否跑完了5,如果跑完了5位,就加上使用過當前節點的數的個數就得到了答案。

         }

         換一種說法其實就是在字典樹上跑A這個數,如果跑到A這個數的某一節點的時候,此時恰好k的這位爲0,那麼就加上那一位上s[i]^1方向的所有跑過的數字的數量

         Ps:爲什麼從高位到低位,要是有人不明白就按照上面的方法看看吧!然後你會發現因爲高位的值的不確定導致你不能判斷。

}

於是乎我們就可以開始寫代碼了。

Ps:這題的數組大小開到1<<24就好了(本菜雞之前因爲數組開小wa成傻逼)

#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
using namespace std;
struct Trie
{
    const static int range=2;
    const static int maxn=1<<24;///字典樹大小
    struct node
    {
        int next[range];
        int cnt;///存該節點的經過數字之和
    } Trienode[maxn];
    int size;
    void init()
    {
        memset(Trienode[0].next,0,sizeof(Trienode[0].next));
        Trienode[0].cnt=0;
        size=1;
    }
    void insert(int s)
    {
        int now=0;
        for(int i=30; i>=0; i--)///存31位
        {
            int c=(s>>i)&1;
            if(!Trienode[now].next[c])///沒有該節點就開闢
            {
                memset(Trienode[size].next,0,sizeof(Trienode[size].next));
                Trienode[size].cnt=0;
                Trienode[now].next[c]=size++;
            }
            now=Trienode[now].next[c];///往後存
            Trienode[now].cnt++;///這個數字經過了該節點,cnt++,因爲第一個節點不算,所以說是先往後走再cnt++
        }
    }
    int find(int s, int k)
    {
        int now=0,i, ans=0;
        for(i=30; i>=0; i--)///找30位
        {
            int c=(s>>i)&1, key=(k>>i)&1;///c:存s[i]的第i位, key:存k的第i位
            if(!key&&Trienode[now].next[c^1])///如果key=0的時候,參照上面的分析
                ans+=Trienode[Trienode[now].next[c^1]].cnt;
            if(Trienode[now].next[c^key])///往s[i]^K的方向越走越遠
                now=Trienode[now].next[c^key];
            else
                break;///沒路
        }
        if(i==-1)///跑完30~0的加上最後一位,其他的則是s[i]^K本身不存在
            ans+=Trienode[now].cnt;
        return ans;
    }
} tree;
int ss[1000010];
int main()
{
    int n, k;
    long long ans=0;///答案會爆long long
    tree.init( );
    tree.insert(0);///爲什麼要插入0呢? 因爲前i項異或和等於s[i]^0,這樣就可以不用特判
    ss[0]=0;///因爲ss[i]^0=ss[i],用在輸入ss[i]時
    scanf("%d%d", &n, &k);
    for(int i=1; i<=n; i++)
    {
        scanf("%d", &ss[i]);
        ss[i]^=ss[i-1];///處理異或前綴和
        ans+=tree.find(ss[i], k);///先找
        tree.insert(ss[i]);///再查
    }
    printf("%I64d\n", ans);
}

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