HDU - 6291 對稱數 (樹上莫隊+分塊) (2018CCPC女生賽)

LP_Cong發表於2018-05-30

對稱數

Time Limit: 30000/15000 MS (Java/Others)    Memory Limit: 512000/512000 K (Java/Others)
Total Submission(s): 70    Accepted Submission(s): 8


Problem Description
小Q認為,偶數具有對稱美,而奇數則沒有。

給定一棵 n個點的樹,任意兩點之間有且僅有一條直接或間接路徑。這些點編號依次為 1 n,其中編號為 i的點上有一個正整數 ai

定義集合 S(u,v) u點到 v點的唯一最短路徑上經過的所有點 x(包括 u v)對應的正整數 ax的集合。小Q將在 m S(u,v)中尋找最小的對稱數。因為偶數具有對稱美,所以對稱數是指那些出現了偶數次(包括 0次)的正整數。

請寫一個程式,幫助小Q找到最小的對稱數。
 

Input
第一行包含一個正整數 T(1T10),表示測試資料的組數。

每組資料第一行包含兩個正整數 n,m(1n,m200000),分別表示點數和詢問數。

第二行包含 n個正整數 a1,a2,...,an(1ai200000),依次表示每個點上的數字。

接下來 n1行,每行兩個正整數 ui,vi(1ui,vin,uivi),表示一條連線 ui vi的雙向樹邊。

接下來 m行,每行兩個正整數 ui,vi(1ui,vin),依次表示每個詢問。
 

Output
對於每個詢問輸出一行一個正整數,即最小的對稱數。
 

Sample Input

 
1 5 3 1 2 2 1 3 1 2 1 3 2 4 2 5 2 3 1 4 2 5
 

Sample Output

 
2 1 1
 

Source
 


解題思路:線段樹做不了,只能考慮樹上莫隊。用一個陣列記錄數字出現的次數,這樣樹上移動可以做到O(1),但是在統計答案的時候,用暴力從小到大看看哪一個數出現次數為偶數的話,複雜度是O(N)的,這樣總體複雜度就變為O(M*N)了。所以考慮分塊。每一塊維護塊內有多少個數字出現的次數為奇數,暴力查詢每一塊,奇數出現次數是否跟塊大小相等,相等的話,答案肯定不在這一塊,所以繼續查詢下一塊。這樣統計答案答案的複雜度是O(sqrt(N))的,加上莫隊,總體複雜度為O(N*sqrt(N)+M*sqrt(M));可接受了!這裡用線上LCA,跑了11000ms,用離線LCA可以去到9000ms。


#include<algorithm>
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<map>
#include<set>
using namespace std;
typedef long long int ll;
const int MAXN=205005;
//輸入掛
inline void scan_d(int &ret)
{
    char c;
    ret = 0;
    while ((c = getchar()) < '0' || c > '9');
    while (c >= '0' && c <= '9')
    {
        ret = ret * 10 + (c - '0'), c = getchar();
    }
}

vector<int> G[MAXN];
int blocksize, blocknum;
int sta[MAXN];
int top;
int deep[MAXN];
int block[MAXN];
int fa[MAXN][25],bin[MAXN];//LCA用
int dfn[MAXN];
int dfs_clock;
int N,Q;
void dfs(int x)
{
    dfn[x]=++dfs_clock;
    int bottom = top;
    for (int i = 1; i < 25; i++)
        if (deep[x] >= bin[i])
            fa[x][i] = fa[fa[x][i - 1]][i - 1];
        else
            break;

    for (int i = 0; i<G[x].size(); i++)
    {
        int to = G[x][i];
        if (to != fa[x][0])
        {
            fa[to][0] = x;
            deep[to] = deep[x] + 1;
            dfs(to);
            if (top - bottom >= blocksize)
            {
                blocknum++;
                while (top != bottom)
                    block[sta[top--]] = blocknum;
            }
        }
        sta[++top] = x;
    }
}

int LCA(int x, int y)
{
    if (deep[x] < deep[y])
        swap(x, y);
    int t = deep[x] - deep[y];
    for (int i = 0; bin[i] <= t; i++)
        if (t & bin[i])
            x = fa[x][i];
    for (int i = 24; i >= 0; i--)
        if (fa[x][i] != fa[y][i])
            x = fa[x][i], y = fa[y][i];
    if (x == y)
        return x;
    return fa[x][0];
}

struct Query{
    int l;
    int r;
    int id;
}q[MAXN];
bool cmp(Query a,Query b){
    if(block[a.l]==block[b.l])
        return dfn[a.r]<dfn[b.r];
    return block[a.l]<block[b.l];
}

int V[MAXN];
bool vis[MAXN];
int ans[MAXN];

//對數字分塊
int bs;
int num[MAXN];//數字出現的次數
int bnum[MAXN];//每一塊內,出現奇數次的數的個數
int blo[MAXN];//每個數字屬於哪一塊

//單點修改
void Work(int x){
    if(vis[x]){
        num[V[x]]--;
        if(num[V[x]]%2){
            bnum[blo[V[x]]]++;
        }
        else
            bnum[blo[V[x]]]--;

        vis[x]=0;
    }
    else{
        num[V[x]]++;
        if(num[V[x]]%2){
            bnum[blo[V[x]]]++;
        }
        else
            bnum[blo[V[x]]]--;
        vis[x]=1;
    }
}

void Move(int x,int y){
    while(x!=y){
        if(deep[x]>deep[y])
            swap(x,y);
        Work(y);
        y=fa[y][0];
    }
}

//獲取答案,這裡是分塊的做法,複雜度為sqrt(MAXA)
int res(){

    for(int i=1;i<=bs+1;i++){
        //如果奇數個數跟塊大小不相等,證明這個塊裡面肯定有一個出現偶數次,在這個塊裡面暴力查詢即可。
        if(bnum[i]!=bs){
            for(int j=(i-1)*bs+1;j<MAXN;j++){
                if(num[j]%2==0){
                    return j;
                }
            }
        }
    }
}


int main(){
    //LCA預處理
    bin[0] = 1;
    for (int i = 1; i < 25; i++)
        bin[i] = bin[i - 1] << 1;

    int T;
    scan_d(T);
    
    //數字分塊
    bs=sqrt(MAXN);
    for(int i=1;i<MAXN;i++)
        blo[i]=(i-1)/bs+1;

    while(T--){
        memset(vis, 0, sizeof(vis));
        memset(num, 0, sizeof(num));
        memset(bnum, 0, sizeof(bnum));
        memset(fa, -1, sizeof(fa));
        memset(deep, 0, sizeof(deep));
        memset(dfn,0,sizeof(dfn));
        dfs_clock=0;
        blocknum=0;

        scan_d(N);
        scan_d(Q);
        blocksize=sqrt(N);
        for(int i=1;i<=N;i++){
            G[i].clear();
            scan_d(V[i]);
        }
        int u,v;
        for(int i=1;i<N;i++){
            scan_d(u);
            scan_d(v);
            G[u].push_back(v);
            G[v].push_back(u);
        }

        dfs(1);
        blocknum++;
        while(top)
            block[sta[top--]]=blocknum;

        for(int i=0;i<Q;i++){
            scan_d(u);
            scan_d(v);
            if(block[u]>block[v])
                swap(u,v);
            q[i].l=u;
            q[i].r=v;
            q[i].id=i;
        }
        sort(q,q+Q,cmp);

        //樹上莫隊
        int lca=LCA(q[0].l,q[0].r);
        Move(q[0].l,q[0].r);
        Work(lca);
        ans[q[0].id]=res();
        Work(lca);
        for(int i=1;i<Q;i++){
            Move(q[i-1].l,q[i].l);
            Move(q[i-1].r,q[i].r);
            lca=LCA(q[i].l,q[i].r);
            Work(lca);
            ans[q[i].id]=res();
            Work(lca);
        }
        for(int i=0;i<Q;i++)
            printf("%d\n",ans[i]);
    }

    return 0;
}












相關文章