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找到最小的對稱數。
給定一棵 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;
}
相關文章
- 分塊 and 莫隊
- 分塊與莫隊
- 分塊莫隊學習筆記筆記
- 搜尋選講、分塊初步、莫隊簡介
- 莫隊
- 24/11/30 ABC381+莫隊+分塊+整體二分學習筆記筆記
- 【2024-ZR-C Day 5】資料結構(3):莫隊(帶修莫隊、回滾莫隊)、邊分治、點分治、樹分治、動態點分治資料結構
- 劃分樹模板+模板題--hdu4251
- hdu--4417Super Mario+劃分樹
- 莫隊詳解
- 神奇的莫隊
- 2021 CCPC女生賽
- 對稱二叉樹二叉樹
- RSA 非對稱加密&解密,超長字串分塊處理加密解密字串
- bzoj3289: Mato的檔案管理(莫隊+樹狀陣列)陣列
- HDU 2689 Sort it【樹狀陣列求逆序對】陣列
- 莫隊演算法演算法
- 根號分治莫隊
- 基礎莫隊模板
- LeetCode 對稱二叉樹LeetCode二叉樹
- 對稱的二叉樹二叉樹
- 莫隊學習筆記筆記
- 101. 對稱二叉樹二叉樹
- 【模板】樹分塊(簡單版)
- L2-008 最長對稱子串 分數 25
- 【演算法學習】莫隊演算法
- 基礎莫隊演算法演算法
- HDU 6274 Master of Sequence(思維+樹狀陣列+二分)AST陣列
- 數論——數論分塊
- 理解區塊鏈的非對稱加密區塊鏈加密
- 17歲中專女生,闖進全球數學競賽12強
- C116 莫隊二次離線 P4887 莫隊二次離線
- JZ-058-對稱的二叉樹二叉樹
- CSP歷年複賽題-P5018 [NOIP2018 普及組] 對稱二叉樹二叉樹
- 線段樹 transformation——hdu 4578ORM
- 回滾莫隊學習筆記筆記
- cf375D. Tree and Queries(莫隊)
- 瞄準空白細分賽道 《風暴島》如何詮釋“非對稱狼人殺”?