HDU - 6291 对称数 (树上莫队+分块) (2018CCPC女生赛)

对称数

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;
}












  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值