对称数
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找到最小的对称数。
给定一棵 n个点的树,任意两点之间有且仅有一条直接或间接路径。这些点编号依次为 1到 n,其中编号为 i的点上有一个正整数 ai。
定义集合 S(u,v)为 u点到 v点的唯一最短路径上经过的所有点 x(包括 u和 v)对应的正整数 ax的集合。小Q将在 m个 S(u,v)中寻找最小的对称数。因为偶数具有对称美,所以对称数是指那些出现了偶数次(包括 0次)的正整数。
请写一个程序,帮助小Q找到最小的对称数。
Input
第一行包含一个正整数
T(1≤T≤10),表示测试数据的组数。
每组数据第一行包含两个正整数 n,m(1≤n,m≤200000),分别表示点数和询问数。
第二行包含 n个正整数 a1,a2,...,an(1≤ai≤200000),依次表示每个点上的数字。
接下来 n−1行,每行两个正整数 ui,vi(1≤ui,vi≤n,ui≠vi),表示一条连接 ui和 vi的双向树边。
接下来 m行,每行两个正整数 ui,vi(1≤ui,vi≤n),依次表示每个询问。
每组数据第一行包含两个正整数 n,m(1≤n,m≤200000),分别表示点数和询问数。
第二行包含 n个正整数 a1,a2,...,an(1≤ai≤200000),依次表示每个点上的数字。
接下来 n−1行,每行两个正整数 ui,vi(1≤ui,vi≤n,ui≠vi),表示一条连接 ui和 vi的双向树边。
接下来 m行,每行两个正整数 ui,vi(1≤ui,vi≤n),依次表示每个询问。
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; }